2 Commits

Author SHA1 Message Date
d32d85556f +md 2026-02-25 10:34:30 +03:00
bc0367ab72 speckit update 2026-02-25 10:31:48 +03:00
9 changed files with 485 additions and 86 deletions

View File

@@ -30,12 +30,12 @@
#
# 5. Multi-Agent Support
# - Handles agent-specific file paths and naming conventions
# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, or Amazon Q Developer CLI
# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, Amazon Q Developer CLI, or Antigravity
# - Can update single agents or all existing agent files
# - Creates default Claude file if no agent files exist
#
# Usage: ./update-agent-context.sh [agent_type]
# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|shai|q|bob|qoder
# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qodercli
# Leave empty to update all existing agent files
set -e
@@ -74,6 +74,7 @@ QODER_FILE="$REPO_ROOT/QODER.md"
AMP_FILE="$REPO_ROOT/AGENTS.md"
SHAI_FILE="$REPO_ROOT/SHAI.md"
Q_FILE="$REPO_ROOT/AGENTS.md"
AGY_FILE="$REPO_ROOT/.agent/rules/specify-rules.md"
BOB_FILE="$REPO_ROOT/AGENTS.md"
# Template file
@@ -618,7 +619,7 @@ update_specific_agent() {
codebuddy)
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI"
;;
qoder)
qodercli)
update_agent_file "$QODER_FILE" "Qoder CLI"
;;
amp)
@@ -630,12 +631,18 @@ update_specific_agent() {
q)
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
;;
agy)
update_agent_file "$AGY_FILE" "Antigravity"
;;
bob)
update_agent_file "$BOB_FILE" "IBM Bob"
;;
generic)
log_info "Generic agent: no predefined context file. Use the agent-specific update script for your agent."
;;
*)
log_error "Unknown agent type '$agent_type'"
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|shai|q|bob|qoder"
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qodercli|generic"
exit 1
;;
esac
@@ -714,7 +721,11 @@ update_all_existing_agents() {
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
found_agent=true
fi
if [[ -f "$AGY_FILE" ]]; then
update_agent_file "$AGY_FILE" "Antigravity"
found_agent=true
fi
if [[ -f "$BOB_FILE" ]]; then
update_agent_file "$BOB_FILE" "IBM Bob"
found_agent=true
@@ -744,7 +755,7 @@ print_summary() {
echo
log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|shai|q|bob|qoder]"
log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qodercli]"
}
#==============================================================================

View File

@@ -0,0 +1,50 @@
# [PROJECT_NAME] Constitution
<!-- Example: Spec Constitution, TaskFlow Constitution, etc. -->
## Core Principles
### [PRINCIPLE_1_NAME]
<!-- Example: I. Library-First -->
[PRINCIPLE_1_DESCRIPTION]
<!-- Example: Every feature starts as a standalone library; Libraries must be self-contained, independently testable, documented; Clear purpose required - no organizational-only libraries -->
### [PRINCIPLE_2_NAME]
<!-- Example: II. CLI Interface -->
[PRINCIPLE_2_DESCRIPTION]
<!-- Example: Every library exposes functionality via CLI; Text in/out protocol: stdin/args → stdout, errors → stderr; Support JSON + human-readable formats -->
### [PRINCIPLE_3_NAME]
<!-- Example: III. Test-First (NON-NEGOTIABLE) -->
[PRINCIPLE_3_DESCRIPTION]
<!-- Example: TDD mandatory: Tests written → User approved → Tests fail → Then implement; Red-Green-Refactor cycle strictly enforced -->
### [PRINCIPLE_4_NAME]
<!-- Example: IV. Integration Testing -->
[PRINCIPLE_4_DESCRIPTION]
<!-- Example: Focus areas requiring integration tests: New library contract tests, Contract changes, Inter-service communication, Shared schemas -->
### [PRINCIPLE_5_NAME]
<!-- Example: V. Observability, VI. Versioning & Breaking Changes, VII. Simplicity -->
[PRINCIPLE_5_DESCRIPTION]
<!-- Example: Text I/O ensures debuggability; Structured logging required; Or: MAJOR.MINOR.BUILD format; Or: Start simple, YAGNI principles -->
## [SECTION_2_NAME]
<!-- Example: Additional Constraints, Security Requirements, Performance Standards, etc. -->
[SECTION_2_CONTENT]
<!-- Example: Technology stack requirements, compliance standards, deployment policies, etc. -->
## [SECTION_3_NAME]
<!-- Example: Development Workflow, Review Process, Quality Gates, etc. -->
[SECTION_3_CONTENT]
<!-- Example: Code review requirements, testing gates, deployment approval process, etc. -->
## Governance
<!-- Example: Constitution supersedes all other practices; Amendments require documentation, approval, migration plan -->
[GOVERNANCE_RULES]
<!-- Example: All PRs/reviews must verify compliance; Complexity must be justified; Use [GUIDANCE_FILE] for runtime development guidance -->
**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->

View File

@@ -3,7 +3,7 @@
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/plan-template.md` for the execution workflow.
## Summary
@@ -22,7 +22,7 @@
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
**Project Type**: [single/web/mobile - determines source structure]
**Project Type**: [e.g., library/cli/web-service/mobile-app/compiler/desktop-app or NEEDS CLARIFICATION]
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]

View File

@@ -392,11 +392,11 @@ def test_status_query_without_task_id_returns_latest_user_task():
# [/DEF:test_status_query_without_task_id_returns_latest_user_task:Function]
# [DEF:test_llm_validation_missing_dashboard_returns_needs_clarification:Function]
# @PURPOSE: LLM validation command without resolvable dashboard id must request clarification instead of generic failure.
# @PRE: Command intent resolves to run_llm_validation but dashboard id cannot be inferred.
# @POST: Assistant response state is needs_clarification with guidance text.
def test_llm_validation_missing_dashboard_returns_needs_clarification():
# [DEF:test_llm_validation_with_dashboard_ref_requires_confirmation:Function]
# @PURPOSE: LLM validation with dashboard_ref should now require confirmation before dispatch.
# @PRE: User sends natural-language validation request with dashboard name (not numeric id).
# @POST: Response state is needs_confirmation since all state-changing operations are now gated.
def test_llm_validation_with_dashboard_ref_requires_confirmation():
_clear_assistant_state()
response = _run_async(
assistant_module.send_message(
@@ -410,8 +410,11 @@ def test_llm_validation_missing_dashboard_returns_needs_clarification():
)
)
assert response.state == "needs_clarification"
assert "Укажите" in response.text or "Missing dashboard_id" in response.text
assert response.state == "needs_confirmation"
assert response.confirmation_id is not None
action_types = {a.type for a in response.actions}
assert "confirm" in action_types
assert "cancel" in action_types
# [/DEF:test_llm_validation_missing_dashboard_returns_needs_clarification:Function]
@@ -555,4 +558,71 @@ def test_list_conversations_archived_only_filters_active():
# [/DEF:test_list_conversations_archived_only_filters_active:Function]
# [DEF:test_guarded_operation_always_requires_confirmation:Function]
# @PURPOSE: Non-dangerous (guarded) commands must still require confirmation before execution.
# @PRE: Admin user sends a backup command that was previously auto-executed.
# @POST: Response state is needs_confirmation with confirm and cancel actions.
def test_guarded_operation_always_requires_confirmation():
_clear_assistant_state()
response = _run_async(
assistant_module.send_message(
request=assistant_module.AssistantMessageRequest(
message="сделай бэкап окружения dev"
),
current_user=_admin_user(),
task_manager=_FakeTaskManager(),
config_manager=_FakeConfigManager(),
db=_FakeDb(),
)
)
assert response.state == "needs_confirmation"
assert response.confirmation_id is not None
action_types = {a.type for a in response.actions}
assert "confirm" in action_types
assert "cancel" in action_types
assert "Выполнить" in response.text or "Подтвердите" in response.text
# [/DEF:test_guarded_operation_always_requires_confirmation:Function]
# [DEF:test_guarded_operation_confirm_roundtrip:Function]
# @PURPOSE: Guarded operation must execute successfully after explicit confirmation.
# @PRE: Admin user sends a non-dangerous migration command (dev → dev).
# @POST: After confirmation, response transitions to started/success with task_id.
def test_guarded_operation_confirm_roundtrip():
_clear_assistant_state()
task_manager = _FakeTaskManager()
db = _FakeDb()
first = _run_async(
assistant_module.send_message(
request=assistant_module.AssistantMessageRequest(
message="запусти миграцию с dev на dev для дашборда 5"
),
current_user=_admin_user(),
task_manager=task_manager,
config_manager=_FakeConfigManager(),
db=db,
)
)
assert first.state == "needs_confirmation"
assert first.confirmation_id
second = _run_async(
assistant_module.confirm_operation(
confirmation_id=first.confirmation_id,
current_user=_admin_user(),
task_manager=task_manager,
config_manager=_FakeConfigManager(),
db=db,
)
)
assert second.state == "started"
assert second.task_id is not None
# [/DEF:test_guarded_operation_confirm_roundtrip:Function]
# [/DEF:backend.src.api.routes.__tests__.test_assistant_api:Module]

View File

@@ -918,6 +918,46 @@ def _coerce_intent_entities(intent: Dict[str, Any]) -> Dict[str, Any]:
# [/DEF:_coerce_intent_entities:Function]
# Operations that are read-only and do not require confirmation.
_SAFE_OPS = {"show_capabilities", "get_task_status"}
# [DEF:_confirmation_summary:Function]
# @PURPOSE: Build human-readable confirmation prompt for an intent before execution.
# @PRE: intent contains operation and entities fields.
# @POST: Returns descriptive Russian-language text ending with confirmation prompt.
def _confirmation_summary(intent: Dict[str, Any]) -> str:
operation = intent.get("operation", "")
entities = intent.get("entities", {})
descriptions: Dict[str, str] = {
"create_branch": "создание ветки{branch} для дашборда{dashboard}",
"commit_changes": "коммит изменений для дашборда{dashboard}",
"deploy_dashboard": "деплой дашборда{dashboard} в окружение{env}",
"execute_migration": "миграция дашборда{dashboard} с{src} на{tgt}",
"run_backup": "бэкап окружения{env}{dashboard}",
"run_llm_validation": "LLM-валидация дашборда{dashboard}{env}",
"run_llm_documentation": "генерация документации для датасета{dataset}{env}",
}
template = descriptions.get(operation)
if not template:
return "Подтвердите выполнение операции или отмените."
def _label(value: Any, prefix: str = " ") -> str:
return f"{prefix}{value}" if value else ""
dashboard = entities.get("dashboard_id") or entities.get("dashboard_ref")
text = template.format(
branch=_label(entities.get("branch_name")),
dashboard=_label(dashboard),
env=_label(entities.get("environment") or entities.get("target_env")),
src=_label(entities.get("source_env")),
tgt=_label(entities.get("target_env")),
dataset=_label(entities.get("dataset_id")),
)
return f"Выполнить: {text}. Подтвердите или отмените."
# [/DEF:_confirmation_summary:Function]
# [DEF:_clarification_text_for_intent:Function]
# @PURPOSE: Convert technical missing-parameter errors into user-facing clarification prompts.
# @PRE: state was classified as needs_clarification for current intent/error combination.
@@ -1328,14 +1368,7 @@ async def send_message(
except Exception as exc:
logger.warning(f"[assistant.planner][fallback] Planner error: {exc}")
if not intent:
intent = {
"domain": "unknown",
"operation": "clarify",
"entities": {},
"confidence": 0.0,
"risk_level": "safe",
"requires_confirmation": False,
}
intent = _parse_command(request.message, config_manager)
confidence = float(intent.get("confidence", 0.0))
if intent.get("domain") == "unknown" or confidence < 0.6:
@@ -1358,7 +1391,8 @@ async def send_message(
try:
_authorize_intent(intent, current_user)
if intent.get("requires_confirmation"):
operation = intent.get("operation")
if operation not in _SAFE_OPS:
confirmation_id = str(uuid.uuid4())
confirm = ConfirmationRecord(
id=confirmation_id,
@@ -1371,7 +1405,7 @@ async def send_message(
)
CONFIRMATIONS[confirmation_id] = confirm
_persist_confirmation(db, confirm)
text = "Операция рискованная. Подтвердите выполнение или отмените."
text = _confirmation_summary(intent)
_append_history(
user_id,
conversation_id,
@@ -1388,7 +1422,13 @@ async def send_message(
text,
state="needs_confirmation",
confirmation_id=confirmation_id,
metadata={"intent": intent},
metadata={
"intent": intent,
"actions": [
{"type": "confirm", "label": "✅ Подтвердить", "target": confirmation_id},
{"type": "cancel", "label": "❌ Отменить", "target": confirmation_id},
],
},
)
audit_payload = {
"decision": "needs_confirmation",
@@ -1406,12 +1446,13 @@ async def send_message(
intent=intent,
confirmation_id=confirmation_id,
actions=[
AssistantAction(type="confirm", label="Confirm", target=confirmation_id),
AssistantAction(type="cancel", label="Cancel", target=confirmation_id),
AssistantAction(type="confirm", label="✅ Подтвердить", target=confirmation_id),
AssistantAction(type="cancel", label="❌ Отменить", target=confirmation_id),
],
created_at=datetime.utcnow(),
)
# Read-only operations execute immediately
text, task_id, actions = await _dispatch_intent(intent, current_user, task_manager, config_manager, db)
state = "started" if task_id else "success"
_append_history(user_id, conversation_id, "assistant", text, state=state, task_id=task_id)

View File

@@ -88,6 +88,20 @@ class DashboardDetailResponse(BaseModel):
dataset_count: int
# [/DEF:DashboardDetailResponse:DataClass]
# [DEF:DatabaseMapping:DataClass]
class DatabaseMapping(BaseModel):
source_db: str
target_db: str
source_db_uuid: Optional[str] = None
target_db_uuid: Optional[str] = None
confidence: float
# [/DEF:DatabaseMapping:DataClass]
# [DEF:DatabaseMappingsResponse:DataClass]
class DatabaseMappingsResponse(BaseModel):
mappings: List[DatabaseMapping]
# [/DEF:DatabaseMappingsResponse:DataClass]
# [DEF:get_dashboards:Function]
# @PURPOSE: Fetch list of dashboards from a specific environment with Git status and last task status
# @PRE: env_id must be a valid environment ID
@@ -168,12 +182,54 @@ async def get_dashboards(
raise HTTPException(status_code=503, detail=f"Failed to fetch dashboards: {str(e)}")
# [/DEF:get_dashboards:Function]
# [DEF:get_database_mappings:Function]
# @PURPOSE: Get database mapping suggestions between source and target environments
# @PRE: User has permission plugin:migration:read
# @PRE: source_env_id and target_env_id are valid environment IDs
# @POST: Returns list of suggested database mappings with confidence scores
# @PARAM: source_env_id (str) - Source environment ID
# @PARAM: target_env_id (str) - Target environment ID
# @RETURN: DatabaseMappingsResponse - List of suggested mappings
# @RELATION: CALLS -> MappingService.get_suggestions
@router.get("/db-mappings", response_model=DatabaseMappingsResponse)
async def get_database_mappings(
source_env_id: str,
target_env_id: str,
mapping_service=Depends(get_mapping_service),
_ = Depends(has_permission("plugin:migration", "READ"))
):
with belief_scope("get_database_mappings", f"source={source_env_id}, target={target_env_id}"):
try:
# Get mapping suggestions using MappingService
suggestions = await mapping_service.get_suggestions(source_env_id, target_env_id)
# Format suggestions as DatabaseMapping objects
mappings = [
DatabaseMapping(
source_db=s.get('source_db', ''),
target_db=s.get('target_db', ''),
source_db_uuid=s.get('source_db_uuid'),
target_db_uuid=s.get('target_db_uuid'),
confidence=s.get('confidence', 0.0)
)
for s in suggestions
]
logger.info(f"[get_database_mappings][Coherence:OK] Returning {len(mappings)} database mapping suggestions")
return DatabaseMappingsResponse(mappings=mappings)
except Exception as e:
logger.error(f"[get_database_mappings][Coherence:Failed] Failed to get database mappings: {e}")
raise HTTPException(status_code=503, detail=f"Failed to get database mappings: {str(e)}")
# [/DEF:get_database_mappings:Function]
# [DEF:get_dashboard_detail:Function]
# @PURPOSE: Fetch detailed dashboard info with related charts and datasets
# @PRE: env_id must be valid and dashboard_id must exist
# @POST: Returns dashboard detail payload for overview page
# @RELATION: CALLS -> SupersetClient.get_dashboard_detail
@router.get("/{dashboard_id}", response_model=DashboardDetailResponse)
@router.get("/{dashboard_id:int}", response_model=DashboardDetailResponse)
async def get_dashboard_detail(
dashboard_id: int,
env_id: str,
@@ -337,60 +393,4 @@ async def backup_dashboards(
raise HTTPException(status_code=503, detail=f"Failed to create backup task: {str(e)}")
# [/DEF:backup_dashboards:Function]
# [DEF:DatabaseMapping:DataClass]
class DatabaseMapping(BaseModel):
source_db: str
target_db: str
source_db_uuid: Optional[str] = None
target_db_uuid: Optional[str] = None
confidence: float
# [/DEF:DatabaseMapping:DataClass]
# [DEF:DatabaseMappingsResponse:DataClass]
class DatabaseMappingsResponse(BaseModel):
mappings: List[DatabaseMapping]
# [/DEF:DatabaseMappingsResponse:DataClass]
# [DEF:get_database_mappings:Function]
# @PURPOSE: Get database mapping suggestions between source and target environments
# @PRE: User has permission plugin:migration:read
# @PRE: source_env_id and target_env_id are valid environment IDs
# @POST: Returns list of suggested database mappings with confidence scores
# @PARAM: source_env_id (str) - Source environment ID
# @PARAM: target_env_id (str) - Target environment ID
# @RETURN: DatabaseMappingsResponse - List of suggested mappings
# @RELATION: CALLS -> MappingService.get_suggestions
@router.get("/db-mappings", response_model=DatabaseMappingsResponse)
async def get_database_mappings(
source_env_id: str,
target_env_id: str,
mapping_service=Depends(get_mapping_service),
_ = Depends(has_permission("plugin:migration", "READ"))
):
with belief_scope("get_database_mappings", f"source={source_env_id}, target={target_env_id}"):
try:
# Get mapping suggestions using MappingService
suggestions = await mapping_service.get_suggestions(source_env_id, target_env_id)
# Format suggestions as DatabaseMapping objects
mappings = [
DatabaseMapping(
source_db=s.get('source_db', ''),
target_db=s.get('target_db', ''),
source_db_uuid=s.get('source_db_uuid'),
target_db_uuid=s.get('target_db_uuid'),
confidence=s.get('confidence', 0.0)
)
for s in suggestions
]
logger.info(f"[get_database_mappings][Coherence:OK] Returning {len(mappings)} database mapping suggestions")
return DatabaseMappingsResponse(mappings=mappings)
except Exception as e:
logger.error(f"[get_database_mappings][Coherence:Failed] Failed to get database mappings: {e}")
raise HTTPException(status_code=503, detail=f"Failed to get database mappings: {str(e)}")
# [/DEF:get_database_mappings:Function]
# [/DEF:backend.src.api.routes.dashboards:Module]

View File

@@ -644,7 +644,12 @@
<div class="mt-3 flex flex-wrap gap-2">
{#each message.actions as action}
<button
class="rounded-md border border-slate-300 px-2.5 py-1 text-xs font-medium text-slate-700 transition hover:bg-slate-100"
class="rounded-md border px-3 py-1.5 text-xs font-semibold transition
{action.type === 'confirm'
? 'border-emerald-300 bg-emerald-50 text-emerald-700 hover:bg-emerald-100'
: action.type === 'cancel'
? 'border-rose-300 bg-rose-50 text-rose-700 hover:bg-rose-100'
: 'border-slate-300 bg-white text-slate-700 hover:bg-slate-100'}"
on:click={() => handleAction(action, message)}
>
{action.label}

View File

@@ -0,0 +1,165 @@
# Feature Specification: [FEATURE NAME]
**Feature Branch**: `[###-feature-name]`
**Reference UX**: `[ux_reference.md]` (See specific folder)
**Created**: [DATE]
**Status**: Draft
**Input**: User description: "$ARGUMENTS"
## User Scenarios & Testing *(mandatory)*
<!--
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
you should still have a viable MVP (Minimum Viable Product) that delivers value.
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
Think of each story as a standalone slice of functionality that can be:
- Developed independently
- Tested independently
- Deployed independently
- Demonstrated to users independently
-->
### User Story 1 - [Brief Title] (Priority: P1)
[Describe this user journey in plain language]
**Why this priority**: [Explain the value and why it has this priority level]
**Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"]
**Acceptance Scenarios**:
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
2. **Given** [initial state], **When** [action], **Then** [expected outcome]
---
### User Story 2 - [Brief Title] (Priority: P2)
[Describe this user journey in plain language]
**Why this priority**: [Explain the value and why it has this priority level]
**Independent Test**: [Describe how this can be tested independently]
**Acceptance Scenarios**:
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
---
### User Story 3 - [Brief Title] (Priority: P3)
[Describe this user journey in plain language]
**Why this priority**: [Explain the value and why it has this priority level]
**Independent Test**: [Describe how this can be tested independently]
**Acceptance Scenarios**:
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
---
[Add more user stories as needed, each with an assigned priority]
### Edge Cases
<!--
ACTION REQUIRED: The content in this section represents placeholders.
Fill them out with the right edge cases.
-->
- What happens when [boundary condition]?
- How does system handle [error scenario]?
## Requirements *(mandatory)*
<!--
ACTION REQUIRED: The content in this section represents placeholders.
Fill them out with the right functional requirements.
-->
### Functional Requirements
- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"]
- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"]
- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"]
- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"]
- **FR-005**: System MUST [behavior, e.g., "log all security events"]
*Example of marking unclear requirements:*
- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?]
- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified]
### Key Entities *(include if feature involves data)*
- **[Entity 1]**: [What it represents, key attributes without implementation]
- **[Entity 2]**: [What it represents, relationships to other entities]
## Success Criteria *(mandatory)*
<!--
ACTION REQUIRED: Define measurable success criteria.
These must be technology-agnostic and measurable.
-->
### Measurable Outcomes
- **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"]
- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"]
- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"]
- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"]
---
## Test Data Fixtures *(recommended for CRITICAL components)*
<!--
Define reference/fixture data for testing CRITICAL tier components.
This data will be used by the Tester Agent when writing unit tests.
Format: JSON or YAML that matches the component's data structures.
-->
### Fixtures
```yaml
# Example fixture format
fixture_name:
description: "Description of this test data"
data:
# JSON or YAML data structure
```
### Example: Dashboard API
```yaml
valid_dashboard:
description: "Valid dashboard object for API responses"
data:
id: 1
title: "Sales Report"
slug: "sales"
git_status:
branch: "main"
sync_status: "OK"
last_task:
task_id: "task-123"
status: "SUCCESS"
empty_dashboards:
description: "Empty dashboard list response"
data:
dashboards: []
total: 0
page: 1
error_not_found:
description: "404 error response"
data:
detail: "Dashboard not found"
```

View File

@@ -0,0 +1,57 @@
# UX Reference: ID Sync Service & Cross-Filter Restoration
**Feature Branch**: `022-id-sync-cross-filter`
**Created**: 2026-02-25
**Status**: Draft
## 1. User Persona & Context
* **Who is the user?**: Data Engineer / Superset Administrator.
* **What is their goal?**: Migrate dashboards between environments (e.g., DEV to PROD) while ensuring that cross-filters remain functional without manual fixing.
* **Context**: Using the Migration Dashboard UI to trigger a dashboard transfer.
## 2. The "Happy Path" Narrative
The user selects a dashboard for migration and notices a pre-checked option: "Fix Cross-Filters". They proceed with the migration. Behind the scenes, the system identifies that some charts are new on the target environment. It performs an initial import, automatically fetches the newly assigned IDs, patches the dashboard's internal metadata, and re-imports it. The user receives a success notification, opens the dashboard on the target environment, and finds that all cross-filters work perfectly on the first try.
## 3. Interface Mockups
### UI Layout & Flow
**Screen/Component**: Migration Launch Modal
* **Key Elements**:
* **[Checkbox] Fix Cross-Filters**:
* **Label**: "Исправить связи кросс-фильтрации (Fix Cross-Filters)"
* **Default**: Checked.
* **Description**: "Автоматически заменяет ID чартов и датасетов во внутренних настройках дашборда, чтобы фильтры работали корректно на целевом стенде".
* **[Button] Start Migration**: Primary action.
* **States**:
* **Processing**: A multi-step progress bar shows:
1. Exporting from Source...
2. Analyzing Metadata...
3. [If New Objects] Performing Preliminary Import...
4. Syncing Remote IDs...
5. Patching Cross-Filters...
6. Finalizing Migration...
* **Success**: Toast notification: "Migration complete. Cross-filters successfully mapped and fixed."
## 4. The "Error" Experience
### Scenario A: Mapping Conflict
* **User Action**: Starts migration.
* **System Response**:
* (UI) Error message: "Conflict detected: Multiple UUIDs found for the same resource name on Target. Please verify environment sync."
* **Recovery**: User is directed to the "Environment Sync" status page to trigger a manual refresh or resolve duplicates.
### Scenario B: ID Sync Failure
* **System Response**: "Unable to retrieve new IDs from Target Superset after initial import. Migration paused."
* **Recovery**: "Retry Sync" button or "Continue without Fixing" option.
## 5. Tone & Voice
* **Style**: Professional, Technical, Reassuring.
* **Terminology**: Use "Target Environment", "Integer ID", "UUID", "Cross-Filtering".