== User stories == US1: Контекст с дашборда/датасета → /agent с URL params US2: Guardrails card — env badge, 7 risk tones, countdown, permission_denied US3: Tools optimization — retry, timeout, summarise, RBAC + context affinity == Backend == - _context.py (NEW): UIContext validation (7 checks) - _tool_filter.py (NEW): RBAC + context affinity pipeline - _confirmation.py: build_confirmation_contract_v2, permission_denied_payload - tools.py: superset_list_databases, retry/summarise/timeout wrappers - app.py: _inject_uicontext, _inject_env_id_into_tools, database prefetch в runtime context - _persistence.py: prefetch_databases() - agent_superset_explore.py: GET /databases endpoint - _llm_async_http.py, _persistence.py: fix double /v1 в LLM URL (LM Studio Unexpected endpoint) == Frontend == - AgentChatModel.svelte.ts: 5 atoms, 3 actions, countdown, context - AgentChat.svelte: production banner, process steps, debug panel - ConfirmationCard.svelte: 7 risk tones, permission_denied, countdown - ToolCallCard.svelte: retrying/timeout/cancelled states - StreamProcessor.svelte.ts: tool_retry, timeout, permission_denied - TopNavbar: sparkles icon + Ассистент - sidebarNavigation: AI section - DashboardHeader, datasets/+page: contextual AI buttons - Icon: sparkles, brain, cpu icons - tailwind: assistant category colors - i18n: en/ru nav keys == Tests == - 159 backend agent tests (+16 US3: retry, timeout, summarise, contracts) - 2544 frontend tests (+11 model + component tests) - 15 JSON fixtures (10 API + 5 model) == Specs == - specs/035-agent-chat-context/: spec, UX, plan, tasks, research, data-model, contracts, quickstart, traceability, fixtures, checklists Closes #035
14 KiB
#region Tasks [C:3] [TYPE ADR] [SEMANTICS tasks,implementation,agent-chat] @defgroup Tasks Implementation tasks for 035-agent-chat-context v2 — Path B model with /agent page, two-layer RBAC, permission_denied flow, and structured truncation.
Input: Design documents from /specs/035-agent-chat-context/
Prerequisites: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, contracts/modules.md ✅
Format: [ID] [P?] [Story] Description
Phase 1: Setup
- T001 [P] Materialize API fixtures from
specs/035-agent-chat-context/fixtures/api/intobackend/tests/fixtures/agent/ - T002 [P] Materialize model fixtures from
specs/035-agent-chat-context/fixtures/model/intofrontend/src/lib/models/__fixtures__/agent/ - T003 Verify toolchain:
cd backend && source .venv/bin/activate && python -m pytest --co -q,cd frontend && npm run test -- --run
Phase 2: Foundational
-
T004 [P] Define UIContext, ConfirmMetadataV2, PermissionDeniedMetadata, extended ToolCall in
frontend/src/lib/models/AgentChatTypes.ts@POST: UIContext with objectType, objectId, objectName(string|null max 256), envId, route, contextVersion(1) @POST: PermissionDeniedMetadata {type, tool_name, required_role, user_role, alternatives} @POST: ToolCallStatus extended with "retrying"|"timeout"|"cancelled"; ToolCall +isWriteTool? -
T005 [P] Create
backend/src/agent/_context.py— UIContext validation @POST: validate_uicontext(payload) → validated dict or ValidationError @TEST_EDGE: valid→returns, invalid_objectType→422, oversized>4KB→413, objectName>256→422 -
T006 Create
backend/src/agent/_tool_filter.py— tool selection pipeline + invocation guard @POST: build_tool_pipeline(tools, user_role, object_type) → filtered list. Order: RBAC→context. @POST: enforce_tool_permission(tool_name, user_role) → allowed or yield permission_denied SSE @POST: _CONTEXT_TOOL_AFFINITY dict (dashboard 9, dataset 8, migration 5 tools) @POST: _TOOL_PERMISSIONS dict (7 admin-only tools) @RATIONALE: Ordered pipeline. Two-layer enforcement per AGTL-FR-005. @REJECTED: Embedding-first routing — postponed to post-MVP (AGTL-FR-006).
Phase 3: User Story 1 — Context-Aware Agent (P1)
Goal: Agent on /agent page receives context from URL params, filters tools by objectType.
Tests for US1
-
T007 [P] [US1] L1 model test for setUIContextFromParams + contextPillLabel in
frontend/src/lib/models/__tests__/AgentChatModel.context.test.ts@TEST_FIXTURE: full_params → uiContext populated, pill="📋 dashboard #42 · ss-dev" @TEST_FIXTURE: no_params → uiContext null, pill="⚪ Контекст не выбран" @TEST_FIXTURE: malformed_objectType → treated as null -
T008 [P] [US1] Contract test for agent_handler uicontext in
backend/tests/test_agent_context.py@TEST_FIXTURE: null→22 tools, dashboard→9 tools, invalid_json→no error+audit log, malformed_objectType→422 -
T009 [P] [US1] Contract test for build_tool_pipeline in
backend/tests/test_agent_tool_filter.py@TEST_FIXTURE: null→all RBAC-allowed, dashboard+analyst→dashboard tools minus admin, unknown→graceful fallback
Implementation for US1
-
T010 [US1] Implement setUIContextFromParams + contextPillLabel in
frontend/src/lib/models/AgentChatModel.svelte.ts@ACTION: parse URLSearchParams, validate objectType enum, set uiContext with contextVersion=1 @POST: contextPillLabel derived: icon+type+id+env or "⚪ Контекст не выбран" -
T011 [US1] Implement onMount context init in
frontend/src/routes/agent/+page.svelte@RELATION BINDS_TO -> [AgentChat.Model] import { page } from "$app/stores"; onMount: URLSearchParams($page.url.search) → setUIContextFromParams -
T012 [US1] Implement process steps update in
frontend/src/lib/components/agent/AgentChat.svelteFirst step label uses model.contextPillLabel. Remaining steps unchanged. @RELATION BINDS_TO -> [AgentChat.Model] -
T013 [US1] Implement _inject_uicontext in
backend/src/agent/app.py@POST: Appends [USER CONTEXT — informational] block. Explicitly marked "not instructions" for prompt-injection protection. -
T014 [US1] Implement agent_handler uicontext param in
backend/src/agent/app.py@PRE: uicontext_str at position 6 (appended, default None). Validated via validate_uicontext. @POST: injected into runtime context. Tools filtered via build_tool_pipeline. @SIDE_EFFECT: UIContext logged via logger.reason. Validation failures logged via logger.explore. RATIONALE: Appended at end — backward-compat for existing 6-arg Gradio clients. -
T015 [P] [US1] Implement isProdContext + showProdWarning + prod banner in
frontend/src/lib/models/AgentChatModel.svelte.ts+AgentChat.svelteisProdContext = $derived((uiContext?.envId?.toLowerCase().includes("prod")) || _activeEnvId?.toLowerCase().includes("prod")) Prod banner using<Icon name="warning">from $lib/ui (existing). bg-warning-light background. @UX_STATE: prod_env→bg-warning-light+banner; non_prod→standard -
T016 [US1] Implement updateActiveEnv in
frontend/src/lib/models/AgentChatModel.svelte.ts@ACTION: syncs _activeEnvId AND uiContext.envId to prevent silent desync @POST: both atoms updated. isProdContext $derived recomputes.
Verification for US1
- T017 [US1] Run backend tests:
cd backend && source .venv/bin/activate && python -m pytest tests/test_agent_context.py tests/test_agent_tool_filter.py -v - T018 [US1] Run frontend tests:
cd frontend && npm run test -- --run frontend/src/lib/models/__tests__/AgentChatModel.context.test.ts - T019 [US1] E2E: navigate
/dashboards/10?env_id=ss-dev→ click Agent → /agent?objectType=dashboard&objectId=10&envId=ss-dev → pill shows dashboard context
Phase 4: User Story 2 — Guardrails Refinement (P2)
Goal: Confirmation card with env badge, risk toning, 10s model-owned countdown, permission_denied as separate SSE event.
Tests for US2
-
T020 [P] [US2] Contract test for build_confirmation_contract_v2 + permission denied in
backend/tests/test_agent_confirmation_v2.py@TEST_FIXTURE: deploy_prod→guarded+prod, delete_any→dangerous, read_only→safe @TEST_FIXTURE: analyst_deploy→permission_denied SSE (NOT confirm_required) @TEST_FIXTURE: env_resolution: tool_args.prod > submit.staging > uiContext.dev -
T021 [P] [US2] L2 UX test for ConfirmationCard 7+1 states in
frontend/src/lib/components/assistant/__tests__/ConfirmationCard.ux.test.tsVerify: read→success, write_staging→warning+badge, write_prod→destructive+PROD, write_dev→info Verify: dangerous→destructive+model-owned countdown, permission_denied→muted+lock (separate event)
Implementation for US2
-
T022 [US2] Implement build_confirmation_contract_v2 in
backend/src/agent/_confirmation.py@POST: three-axis risk. Env resolution: tool_args.env_id>submit envId>UIContext.envId>null. Env normalization via substring match. @DATA_CONTRACT: (tool_name, tool_args, user_role, target_env)→ConfirmMetadataV2 RATIONALE: Three-axis replaces prefix heuristic; env resolution prevents guardrail misclassification. -
T023 [US2] Implement yield_permission_denied in
backend/src/agent/_confirmation.py@POST: Yields SSE type="permission_denied" (NOT confirm_required). Bypasses HITL checkpoint. RATIONALE: Security — forbidden calls must not enter guarded checkpoint. -
T024 [US2] Implement enforce_tool_permission invocation guard in
backend/src/agent/_tool_filter.py@POST: Called before every tool execution. Checks user_role vs _TOOL_PERMISSIONS. Denied→yield permission_denied. @RATIONALE: Two-layer enforcement — catches hallucinated/replayed/direct calls bypassing prompt filter. -
T025 [US2] Implement ConfirmationCard env badge + 7 risk tones in
frontend/src/lib/components/assistant/ConfirmationCard.svelteUses<Button>from $lib/ui (existing),<Icon>from $lib/ui (existing) @UX_STATE: 7 states (read, write_dev, write_staging, write_prod, dangerous, loading_confirm, loading_deny) -
T026 [US2] Implement model-owned dangerous countdown in
frontend/src/lib/models/AgentChatModel.svelte.tsstartDangerousCountdown(): setInterval 1s decrement on this.dangerousCountdown. Component reads model atom only. @RATIONALE: Single source of truth — eliminates model/component timer race condition. -
T027 [US2] Implement dangerous countdown UI in
frontend/src/lib/components/assistant/ConfirmationCard.svelteButton disabled while model.dangerousCountdown>0. Text: "💀 Удалить (N)". aria-live="assertive" every 1s. Reads model.dangerousCountdown — no component timer. -
T028 [US2] Implement permission_denied handler in
frontend/src/lib/models/AgentChat.StreamProcessor.svelte.tscase "permission_denied": render card with 🔒, required_role, alternatives. "Закрыть" only. -
T029 [US2] Implement cancelled tool card for deny flow in
frontend/src/lib/models/AgentChat.StreamProcessor.svelte.ts+ToolCallCard.svelteconfirm_resolved deny → push ToolCall status="cancelled". Also yield agent message "⏹️ Операция отменена".
Verification for US2
- T030 [US2] Run backend tests:
cd backend && source .venv/bin/activate && python -m pytest tests/test_agent_confirmation_v2.py -v - T031 [US2] Run frontend tests:
cd frontend && npm run test -- --run frontend/src/lib/components/assistant/__tests__/ConfirmationCard.ux.test.ts - T032 [US2] Manual: deploy to prod→destructive+PROD badge. delete→10s countdown. analyst→permission_denied (no confirm button)
Phase 5: User Story 3 — Tool Optimisation (P3)
Goal: Fixed-delay retry, structured truncation, configurable timeout (write-safe), invocation RBAC guard.
Tests for US3
-
T033 [P] [US3] Contract test for _retry_read_tool in
backend/tests/test_agent_retry.py@TEST_FIXTURE: first_502→retries+success, both_502→exhausted, write_tool→no retry -
T034 [P] [US3] Contract test for _summarise_response in
backend/tests/test_agent_summarise.pyarray 50→"Found 50...+45 more", object→keys+sample, text→sentence-boundary truncation, short→unchanged -
T035 [P] [US3] Contract test for _execute_with_timeout in
backend/tests/test_agent_timeout.py@TEST_EDGE: complete_under→normal, read_exceed→retryable timeout, write_exceed→retryable=false status_unknown -
T036 [P] [US3] L2 UX test for ToolCallCard states in
frontend/src/lib/components/assistant/__tests__/ToolCallCard.ux.test.tsretrying→spinner+attempt, read_timeout→⏱️+retry button, write_timeout→⏱️+"check status" (no retry), cancelled→🚫
Implementation for US3
-
T037 [US3] Implement _retry_read_tool in
backend/src/agent/tools.py@PRE: tool risk_level="safe". Error 5xx/ConnectError. Fixed 1s asyncio.sleep. @POST: retry once. Yields tool_retry SSE. Exhaust→raises retry_exhausted. RATIONALE: Single fixed retry catches ~80% transient failures. Read-only only. -
T038 [US3] Implement _summarise_response in
backend/src/agent/tools.py@POST: >4000 chars + JSON array→"Found N: -item1...+ (N-5) more". Object→keys+sample. Text→sentence-boundary cut. RATIONALE: Structured truncation preserves context better than hard cut. -
T039 [US3] Implement _execute_with_timeout in
backend/src/agent/tools.py@PRE: asyncio.wait_for(tool_fn, 30s). is_write param. @POST: timeout→tool_timeout SSE. Write: is_write_tool=true, retryable=false. RATIONALE: Write tool timeout ≠ mutation didn't happen. No retry — check status instead. -
T040 [US3] Implement tool_retry + tool_timeout handlers in
frontend/src/lib/models/AgentChat.StreamProcessor.svelte.tscase "tool_retry": ToolCall status="retrying". case "tool_timeout": status="timeout", isWriteTool→no retry button. -
T041 [US3] Implement retry+timeout states in
frontend/src/lib/components/assistant/ToolCallCard.svelteretrying: "Повторная попытка... ({N}/{M})". Read timeout: ⏱️+retry btn. Write timeout: ⏱️+"Operation status unknown" (no btn). Uses<Button variant="secondary" size="xs">from $lib/ui (existing). -
T042 [US3] Implement debug panel context sections in
frontend/src/lib/components/agent/AgentChat.svelteAdd "Контекст страницы" (JSON.stringify uiContext) + "Pipeline" (effective tool list after filtering) sections to debug panel. Tool list data comes from backend SSE debug event (pipeline_result) — NOT frontend reconstruction.
Verification for US3
- T043 [US3] Run backend tests:
cd backend && source .venv/bin/activate && python -m pytest tests/test_agent_retry.py tests/test_agent_summarise.py tests/test_agent_timeout.py -v - T044 [US3] Run frontend tests:
cd frontend && npm run test -- --run frontend/src/lib/components/assistant/__tests__/ToolCallCard.ux.test.ts - T045 [US3] Manual: 502→auto-retry→success. Large→top-5. Analyst→write tools excluded+invocation guard active.
Phase 6: Polish & Cross-Cutting
- T046 [P] Full backend:
cd backend && source .venv/bin/activate && python -m pytest -v - T047 [P] Full frontend:
cd frontend && npm run test - T048 [P] Backend lint:
cd backend && python -m ruff check . - T049 [P] Frontend lint:
cd frontend && npm run lint - T050 [P] Frontend build:
cd frontend && npm run build - T051 Attention compliance audit — verify ATTN_1-4 on all new/changed contracts
- T052 E2E verification per quickstart.md
- T053 Run quickstart validation
Dependencies
Phase 1 → Phase 2 → US1(P1) → US2(P2) ∥ US3(P3) → Phase 6
US2 and US3 can proceed in parallel after US1 base.
Parallel Opportunities
Phase 1: T001∥T002 | Phase 2: T004∥T005∥T006 | US1: T007∥T008∥T009, T015∥(T010→T011) | US2: T020∥T021 | US3: T033∥T034∥T035∥T036 | Phase 6: T046∥T047∥T048∥T049∥T050
Notes
- Gradio uicontext at position END (6), not middle — backward-compat
- Permission denied: SSE type="permission_denied", bypasses HITL
- Countdown: model-owned, component reads only
- Write timeout: no retry
- No initialContext in store — context from URL params only
- RBAC: two-layer (prompt filter + invocation guard)
#endregion Tasks