10 Commits
Author SHA1 Message Date
Juan Denis 55d8fb1e44 Read route bodies through json_object()
328 more routes used a JSON body as a dict straight from
request.get_json(), so a body of null, [] or a bare string crashed them
into a 500. Bare reads become json_object(); the
'get_json(silent=True) or {}' reads of action routes become
json_object(optional=True), which still treats a missing body as {}.
Routes that take arrays or pass the body through untouched are left
alone. The full backend suite shows no new failures.

Also carries three files' bare section citations (apps, backups,
notifications) and the extended citation guard, which needed them.
2026-10-10 20:53:43 -04:00
Juan Denis bebc69f71c feat(ai): connect task routing, guarded fallback, and scoped run accounting 2026-09-08 04:09:27 -04:00
Juan Denis 54028fbff4 fix(ai): enforce assistant enable state and conversation cost limits 2026-09-08 03:03:00 -04:00
Juan Denis fde0acee6d fix(ai): satisfy release gates and prepare 1.10.0 [skip version]
Keep connection persistence behind the service boundary, use typed errors and shared JSON parsing, and refresh reviewed inventories. The new GET connection catalog accepts API keys so API clients managing connections can discover IDs without exposing credentials; existing JWT-only routes stay unchanged. Add regression coverage and release notes.
2026-09-08 01:30:03 -04:00
Juan Denis 569b052659 feat(ai): expose Prompture providers and saved connections 2026-09-08 00:42:54 -04:00
Juan Denis 6bf3490345 fix(ai): enforce resource access and bounded protected chat
Scope built-in tools to the live caller, recheck write authorization after confirmation, redact structured results, and fail closed when protections fail. Bound chat inputs, concurrent turns, and cancellation-aware streaming queues.
2026-09-05 04:29:45 -04:00
Juan DenisandClaude Opus 5 009d9c4d7f fix(ai): raise a typed error for a malformed attachment manifest
The attachment round added two hand-shaped `jsonify({'error': str(exc)}), 400`
returns in api/ai.py, which pushed the error-shape census to 1146 against a
ceiling of 1144 and turned shard 2 red. The census policy is not "raise the
ceiling" -- new endpoints raise, and these are new.

AttachmentValidationError now subclasses app.exceptions.ValidationError, so the
global handler owns the 400 body, the `code`, and the X-Request-ID correlation.
It is still a ValueError (ValidationError subclasses one), so every existing
`except ValueError` caller and the pytest.raises assertions in
test_ai_attachments.py are unchanged, and the two try/except blocks in the
routes disappear entirely.

Census back to 1144 = ceiling. Verified: test_ai_attachments, test_ai_assistant,
test_error_shape_ratchet all green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 20:14:34 -04:00
Juan Denis 3313bd842f feat(ai): persist authorized attachment manifests 2026-08-21 10:53:56 -04:00
Juan DenisandClaude Opus 5 a7d5a0b919 fix(api): a crash the route caught must still reach /monitoring/errors
Plan 76 milestone B, drift/bug row 4. 43 API handlers answered HTTP 500 from
their own `except Exception`, so the global 500 handler never ran — and that
handler was the ONLY caller of error_log_service.record_error(source='backend').
Every one of those crashes was absent from /monitoring/errors, and the page read
empty precisely because someone had "handled" them.

35 of the 43 also returned str(exc) to the caller. Two returned the full
traceback (templates.py, which also print()ed it), and several logged with a
locally-minted uuid4 'ref' that correlated with nothing but a log line.

The recording side of the 500 handler moves to app/error_reporting.py, so it is
importable rather than reachable only by not-catching:

    record_unexpected(exc)   -> logs, rolls the session back, records, returns
                                the request id
    unexpected_response(exc) -> the above plus the documented crash body

The 500 handler now calls the same pair, so there is one recorder rather than
one recorder and 43 routes that quietly opted out of it.

Caller-visible change on those endpoints: the body is now the documented
{'error': 'Internal server error', 'status': 500, 'code': 'internal_error',
'request_id': ...} instead of the exception text. That is the point — an
unexpected exception's text is written for whoever reads the log and routinely
carries connection strings, absolute paths, and SQL. The request id is the part
a caller can act on, and it matches the row in the error log. No frontend code
read the 'ref' or 'trace' keys this drops (checked).

Two sites keep their own wording and call record_unexpected instead: sso.py's
login failures should not read as a generic crash on the login page.
docker.py's bulk endpoint keeps its per-item `results` — the caller's half-done
work survives, only the exception text is dropped.

Ratchet: tests/unreported_crash_census.py + its test, held at **0**, not a
legacy baseline to shrink. It counts blanket handlers that answer 500 without
calling either reporter, and is scoped to app/api — a service catching an
exception and returning a value is making a domain decision, not declaring an
HTTP crash. Verified non-vacuous: an injected handler fails it, and it passes
again on revert.

Measured and deliberately still open: 56 API handlers swallow a crash and
answer 200. A failure reported as success is worse, but converging it is
milestone C's response-envelope work, not this row.

Gate: tests/test_unexpected_error_reporting.py (11, including the two that pin
why the old shape lost the crash), test_unreported_crash_ratchet.py (9), and
the 66 test files covering every touched module: 1105 passed, 7 skipped, 1
pre-existing Windows-only failure (test_fleet_doctor's socket.gaierror).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 20:38:20 -04:00
Juan DenisandClaude Opus 4.8 9cab065dc5 feat(ai): add core AI assistant primitive powered by Prompture
AI is a core ServerKit primitive (not a plugin): an Intercom-style bubble + slide-in drawer on every dashboard page, with a tools+page-context assistant mode and a plain simple chat mode, SSE streaming, conversation persistence, and guarded write actions (the model proposes; the user approves).

Plugins EXTEND the assistant rather than rebuilding it: backend via app.plugins_sdk.ai (reg.tool + register_context_provider, discovered from a plugin.json ai block with install/enable/disable hooks); frontend via useServerkitAI() and a contributions.ai category (suggested_prompts, tool_renderers). The marketplace gains an ai category for discovery.

Backend: ai_service (Prompture Conversation + ProviderEnvironment + guardrails), ai_tool_registry singleton, ai_tools_builtin (core.* tools), the /api/v1/ai blueprint (SSE via a worker-thread + queue bridge, single-gevent-worker safe; in-thread confirmation gate), AiConversation/AiMessage/AiPendingAction models + migration 013, encrypted ai_* settings. Frontend: AIContext + components/ai/*, AI settings tab. Powered by Prompture (installed editable; requirements pinned).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:39:05 -04:00