chore: migrate GRACE-Poly anchors to hierarchical dotted naming

Systematic rename of all semantic anchors (#region, [DEF], @RELATION)
across 1400+ files — backend Python, frontend Svelte/TS, specs, docs:
- Flat anchors become Namespace.Module.Entity
- @RELATION references updated to match new anchor paths
- Zero business logic changes
This commit is contained in:
root
2026-07-22 11:48:15 +03:00
parent 34393adf7e
commit 632b730fff
1438 changed files with 17031 additions and 16802 deletions

View File

@@ -198,6 +198,7 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
# @BRIEF One-line purpose.
# @RELATION DEPENDS_ON -> [DependencyService]
# @RELATION DEPENDS_ON -> [DTO:RequestSchema]
# #endregion Domain.Resource.Action
```
**Full header for C4/C5 orchestration & cross-stack functions:**
@@ -215,6 +216,7 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
# @RATIONALE Why this implementation approach.
# @REJECTED What alternative was considered and forbidden.
# @TEST_EDGE: scenario_name -> Expected failure behavior.
# #endregion Domain.Resource.Action
```
**Screen Model actions (Svelte `.svelte.ts`):**
@@ -227,6 +229,7 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
// @SIDE_EFFECT API call, store mutation, model state update.
// @RELATION CALLS -> [apiClient]
// @TEST_EDGE: network_failure -> ScreenState = "error"
// #endregion ScreenModel.actionName
```
**Rules:**
@@ -264,7 +267,7 @@ specs/<feature>/fixtures/
**`manifest.md` — fixture index with GRACE contracts:**
```markdown
#region FixtureManifest [C:3] [TYPE ADR] [SEMANTICS test,fixture,[DOMAIN]]
#region Example.Fixturemanifest [C:3] [TYPE ADR] [SEMANTICS test,fixture,[DOMAIN]]
@defgroup Fixtures Canonical test fixtures for [FEATURE].
## @{ Fixture FX_Auth.Login.Valid [C:2] [TYPE Block] [SEMANTICS test,auth,fixture]
@@ -287,6 +290,10 @@ specs/<feature>/fixtures/
@TEST_INVARIANT: env_reset_selection -> VERIFIED_BY: [Test.Migration.Model]
@TEST_FIXTURE: env_reset -> fixtures/model/migration_env_reset.json
## @} Fixture FX_Migration.EnvReset
## @} FX_Migration.EnvReset
## @} FX_Auth.Login.MissingPassword
## @} FX_Auth.Login.Valid
# #endregion Example.Fixturemanifest
```
**JSON fixture format:**
@@ -341,7 +348,7 @@ Generate `quickstart.md` using real repository verification paths:
If UX contracts exist (`contracts/ux/` was generated by `/speckit.ux`), generate `traceability.md` — a requirements traceability matrix (RTM) mapping every user story through its implementation chain:
```markdown
#region Traceability [C:3] [TYPE ADR] [SEMANTICS traceability,rtm,[DOMAIN]]
#region Std.Agents.Traceability [C:3] [TYPE ADR] [SEMANTICS traceability,rtm,[DOMAIN]]
@defgroup Trace Matrix Requirements → Model → API → Task → Test for [FEATURE].
## Traceability Matrix
@@ -358,7 +365,7 @@ If UX contracts exist (`contracts/ux/` was generated by `/speckit.ux`), generate
| `GET /api/dashboards` | FX_Dashboards.Hub.* | Test.Dashboards.Hub | /dashboards, /migration |
| `Dashboards.Hub` model | FX_Dashboards.EnvReset | Test.Dashboards.Hub | /dashboards |
#endregion Traceability
#endregion Std.Agents.Traceability
```
**Generation rules:**

View File

@@ -200,7 +200,7 @@ After all questions are answered, create TWO artifacts:
**`contracts/ux/alternatives.md`** — all options considered, BEFORE final choice:
```markdown
#region UxAlternatives [C:3] [TYPE ADR] [SEMANTICS ux,alternatives,[DOMAIN]]
#region Std.Agents.UxAlternatives [C:3] [TYPE ADR] [SEMANTICS ux,alternatives,[DOMAIN]]
@defgroup Ux Design alternatives explored for [FEATURE].
## Screen: [Name]
@@ -225,13 +225,13 @@ After all questions are answered, create TWO artifacts:
- ❌ Rejected: Confirm dialog — extra click on every action, annoying at scale
- ❌ Rejected: No confirmation — dangerous for delete/migrate
#endregion UxAlternatives
#endregion Std.Agents.UxAlternatives
```
**`contracts/ux/decisions.md`** — only the final choices:
```markdown
#region UxDecisions [C:3] [TYPE ADR] [SEMANTICS ux,decisions,[DOMAIN]]
#region Std.Agents.UxDecisions [C:3] [TYPE ADR] [SEMANTICS ux,decisions,[DOMAIN]]
@defgroup Ux Final UX design decisions for [FEATURE].
## Screen: [Name]
@@ -240,7 +240,7 @@ After all questions are answered, create TWO artifacts:
- Data: Paginated (20/page) + search
- Feedback: Undo toast (5s) for destructive actions
#endregion UxDecisions
#endregion Std.Agents.UxDecisions
```
**Rule:** `alternatives.md` shows the DESIGN SPACE — agent can see WHY each path was rejected. `decisions.md` is the compact reference for `/speckit.plan`.

View File

@@ -3,7 +3,7 @@ name: molecular-cot-logging
description: Structured logging protocol for agent-driven development, based on molecular Long CoT bonds (REASON/REFLECT/EXPLORE). Replaces legacy Entry/Exit/Coherence markers. Python + Svelte.
---
#region MolecularCoTLogging [C:5] [TYPE Skill] [SEMANTICS reasoning,runtime,logging,agentic]
#region Std.Agents.MolecularCoTLogging [C:5] [TYPE Skill] [SEMANTICS reasoning,runtime,logging,agentic]
@BRIEF Structured logging protocol for agent-driven development, based on molecular Long CoT bonds (Deep-Reasoning, Self-Reflection, Self-Exploration). Replaces legacy Entry/Exit/Coherence markers.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DISPATCHES -> [Std.Semantics.Python]
@@ -385,4 +385,4 @@ All new C3+ code **must** produce logs an agent can understand with almost no so
See also: semantics-python (belief runtime), semantics-core (region markup rules).
#endregion MolecularCoTLogging
#endregion Std.Agents.MolecularCoTLogging

View File

@@ -99,18 +99,18 @@ Not all GRACE tags are equal in the model's training data. Understanding which t
### Legacy — DEF (permanently recognized)
```python
// [DEF:ContractId:Type]
// [DEF:Std.Agents.ContractId:Type]
// @TAG: value
<code>
// [/DEF:ContractId:Type]
// [/DEF:Std.Agents.ContractId:Type]
```
### Doc — Brace (Markdown, specs, ADRs)
```
## @{ ContractId [C:N] [TYPE TypeName]
## @{ Std.Agents.ContractId [C:N] [TYPE TypeName]
@BRIEF Description
...
## @} ContractId
## @} Std.Agents.ContractId
```
**Allowed Types:** Module, Function, Class, Component, Model, Block, ADR, Tombstone, Skill, Agent.
@@ -262,6 +262,7 @@ The opening anchor MUST pack maximum signal into one line:
```
#region Domain.Sub.ContractId [C:N] [TYPE TypeName] [SEMANTICS tag1,tag2,tag3]
# #endregion Domain.Sub.ContractId
```
- ID, complexity, type, and semantic tags on ONE line → survives CSA 4× pooling as a single KV record.
@@ -305,6 +306,7 @@ Example — both mechanisms reinforce each other:
#region Core.Auth.Login [C:4] [TYPE Function] [SEMANTICS auth,login,token]
# @ingroup Auth
# @BRIEF Authenticate user by credentials.
# #endregion Core.Auth.Login
```
**Rule:** Identical domain = identical primary keyword in `[SEMANTICS ...]` AND identical `@ingroup Domain`. They target different compression layers (CSA vs HCA) and don't conflict — the keyword repetition amplifies the DSA score.

View File

@@ -7,7 +7,7 @@ description: "Python-specific GRACE-Poly protocol: few-shot complexity examples,
@BRIEF Python-specific HOW: few-shot complexity examples, belief runtime patterns, module decomposition, and FastAPI/SQLAlchemy conventions for the GRACE-Poly protocol in superset-tools.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DEPENDS_ON -> [Std.Semantics.Contracts]
@RELATION DISPATCHES -> [MolecularCoTLogging]
@RELATION DISPATCHES -> [Std.Agents.MolecularCoTLogging]
@RESTRICTION EXAMPLES ONLY — this file provides language-specific code patterns. All protocol rules (tier definitions, tag catalog, anchor syntax) are defined exclusively in `semantics-core`. This file MUST NOT redefine or contradict any rule from `semantics-core`.
@RATIONALE Python's async/await model, FastAPI dependency injection, and SQLAlchemy session management create unique failure modes for Transformer agents: (1) async/await boundary confusion — agents write sync code in async contexts or forget `await` on ORM calls, producing silent no-ops; (2) dependency injection blindness — FastAPI's `Depends()` creates implicit call graphs that the agent's attention cannot trace without explicit @RELATION edges; (3) session lifecycle drift — SQLAlchemy sessions have strict boundaries that agents violate by passing detached objects across function calls. Concrete examples at each complexity tier act as few-shot anchors that override the agent's pre-trained (and often wrong) Python patterns.
@REJECTED Generic Python patterns without GRACE anchors were rejected — agents produce working code that violates module size limits (INV_7), omits belief runtime markers, and creates orphan contracts invisible to the semantic index. Relying on the agent's pre-trained FastAPI/SQLAlchemy knowledge without project-specific examples was rejected — superset-tools has specific conventions (trace_id propagation, plugin architecture, WebSocket logging) that general training data cannot capture.

View File

@@ -6,7 +6,7 @@ description: "Svelte 5 (Runes) protocol for superset-tools: UX State Machines, T
#region Std.Semantics.Svelte [C:5] [TYPE Skill] [SEMANTICS frontend,svelte,ui,ux,tailwind]
@BRIEF HOW to build Svelte 5 (Runes) Components for superset-tools with UX State Machines, Tailwind CSS, store topology, and visual-interactive validation.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DEPENDS_ON -> [MolecularCoTLogging]
@RELATION DEPENDS_ON -> [Std.Agents.MolecularCoTLogging]
@RELATION DISPATCHES -> [Std.Semantics.Testing]
@RESTRICTION EXAMPLES ONLY — this file provides language-specific code patterns. All protocol rules (tier definitions, tag catalog, anchor syntax) are defined exclusively in `semantics-core`. UX contract tags are defined here as examples; the tag catalog lives in `semantics-core` §III. This file MUST NOT redefine or contradict any rule from `semantics-core`.
@RATIONALE Svelte 5 runes ($state, $derived, $effect, $props) chosen for reactive precision and native compiler optimisations over Svelte 4 legacy reactivity ($:). Tailwind CSS selected for zero-runtime utility-first styling and rapid visual validation via chrome-devtools MCP. FSM-based UX contracts (@UX_STATE, @UX_FEEDBACK, @UX_RECOVERY) chosen to create verifiable state-transition tests that the browser Judge Agent can execute deterministically. superset-tools internal API wrappers (fetchApi/requestApi) chosen over native fetch to enforce auth, error normalisation, and trace_id propagation. Model-first architecture chosen because event-handler spaghetti is the #1 Transformer failure mode in UI code: the agent scatters logic across onclick/onchange in 5 files — KV-cache cannot hold cross-component relationships, creating invisible coupling that breaks silently.
@@ -309,7 +309,7 @@ Frontend logging uses `log()` from `$lib/cot-logger` per **MolecularCoTLogging**
Region format for HTML/Svelte comments:
```html
<!-- #region MigrationTaskCard [C:3] [TYPE Component] [SEMANTICS ui,migration,task] -->
<!-- #region Std.Semantics.MigrationTaskCard [C:3] [TYPE Component] [SEMANTICS ui,migration,task] -->
<!-- @BRIEF Card displaying a migration task with status, progress, and action buttons. -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [StatusBadge] -->
@@ -410,7 +410,7 @@ Region format for HTML/Svelte comments:
</Button>
</div>
</div>
<!-- #endregion MigrationTaskCard -->
<!-- #endregion Std.Semantics.MigrationTaskCard -->
```
## VII. SS-TOOLS DESIGN TOKEN CANON & COMPONENT REUSE

View File

@@ -90,7 +90,7 @@ from unittest.mock import AsyncMock, patch
class TestDashboardMigration:
"""Verify migrate_dashboard @POST guarantees."""
# #region test_migrate_dashboard_success [C:2] [TYPE Function]
# #region Std.Semantics.TestMigrateDashboardSuccess [C:2] [TYPE Function]
# @BRIEF Happy path: valid dashboard with complete db mapping.
@pytest.mark.asyncio
async def test_migrate_dashboard_success(self):
@@ -98,8 +98,9 @@ class TestDashboardMigration:
expected = {"id": "dash_1", "status": "imported"}
# ... test implementation
pass
# #endregion test_migrate_dashboard_success
# #endregion Std.Semantics.TestMigrateDashboardSuccess
# #endregion TestDashboardMigration
# #endregion Test.Migration.RunTask
```
### Running tests

View File

@@ -1,4 +1,4 @@
# [DEF:Axiom_Tools_Evaluation:Report]
# [DEF:Std.Ai.AxiomToolsEvaluation:Report]
# @COMPLEXITY: 4
# @PURPOSE: Comprehensive evaluation of all axiom-core MCP server tools across 8 UX metrics.
# @LAYER: Analysis
@@ -552,4 +552,4 @@
---
# [/DEF:Axiom_Tools_Evaluation:Report]
# [/DEF:Std.Ai.AxiomToolsEvaluation:Report]

View File

@@ -1,4 +1,4 @@
# [DEF:EffortAssess:Report]
# [DEF:Std.Ai.EffortAssess:Report]
# @COMPLEXITY: 3
# @PURPOSE: Оценка трудозатрат для репозитория на основе эволюции требований в specs и изменений объёма по git-истории.
# @RELATION: DEPENDS_ON -> [Project_Knowledge_Map:Root]
@@ -121,4 +121,4 @@
- Plans: `specs/021-llm-project-assistant/plan.md`, `specs/025-clean-release-compliance/plan.md`, `specs/027-dataset-llm-orchestration/plan.md`.
- Git evidence: коммиты `8406628`, `de1f044`, `36742cd`, `0083d90`, `321e0eb`, `023bacd`, `ed3d5f3`, а также хронологический `git log --reverse -- specs`.
# [/DEF:EffortAssess:Report]
# [/DEF:Std.Ai.EffortAssess:Report]

View File

@@ -1,4 +1,4 @@
#[DEF:BackendRouteShot:Module]
#[DEF:Std.Ai.BackendRouteShot:Module]
# @COMPLEXITY: 3
# @SEMANTICS: Route, Task, API, Async
# @PURPOSE: Reference implementation of a task-based route using GRACE-Poly.
@@ -16,14 +16,14 @@ from ...dependencies import get_task_manager, get_config_manager, get_current_us
router = APIRouter()
# [DEF:CreateTaskRequest:Class]
# [DEF:Std.Ai.CreateTaskRequest:Class]
# @PURPOSE: DTO for task creation payload.
class CreateTaskRequest(BaseModel):
plugin_id: str
params: Dict[str, Any]
# [/DEF:CreateTaskRequest:Class]
# [/DEF:Std.Ai.CreateTaskRequest:Class]
# [DEF:create_task:Function]
# [DEF:Std.Ai.CreateTask:Function]
# @COMPLEXITY: 4
# @PURPOSE: Create and start a new task using TaskManager. Non-blocking.
# @RELATION: [CALLS] ->[task_manager.create_task]
@@ -70,6 +70,6 @@ async def create_task(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal Task Spawning Error"
)
# [/DEF:create_task:Function]
# [/DEF:Std.Ai.CreateTask:Function]
# [/DEF:BackendRouteShot:Module]
# [/DEF:Std.Ai.BackendRouteShot:Module]

View File

@@ -1,4 +1,4 @@
# [DEF:TransactionCore:Module]
# [DEF:Std.Ai.TransactionCore:Module]
# @COMPLEXITY: 5
# @SEMANTICS: Finance, ACID, Transfer, Ledger
# @PURPOSE: Core banking transaction processor with ACID guarantees.
@@ -32,7 +32,7 @@ class TransferResult(NamedTuple):
status: str
new_balance: Decimal
# [DEF:execute_transfer:Function]
# [DEF:Std.Ai.ExecuteTransfer:Function]
# @COMPLEXITY: 5
# @PURPOSE: Atomically move funds between accounts with audit trails.
# @RELATION: [CALLS] ->[atomic_transaction]
@@ -80,6 +80,6 @@ def execute_transfer(sender_id: str, receiver_id: str, amount: Decimal) -> Trans
# GRACE: [EXPLORE] - Неожиданный сбой
logger.explore("Critical Transfer Failure", exc_info=e)
raise RuntimeError("TRANSACTION_ABORTED") from e
#[/DEF:execute_transfer:Function]
#[/DEF:Std.Ai.ExecuteTransfer:Function]
# [/DEF:TransactionCore:Module]
# [/DEF:Std.Ai.TransactionCore:Module]

View File

@@ -1,11 +1,11 @@
<!-- [DEF:FrontendComponentShot:Component] -->
<!-- [DEF:Std.Ai.FrontendComponentShot:Component] -->
<!--
/**
* @COMPLEXITY: 5
* @SEMANTICS: Task, Button, Action, UX
* @PURPOSE: Action button to spawn a new task with full UX feedback cycle.
* @LAYER: UI (Presentation)
* @RELATION: [CALLS] ->[postApi]
* @RELATION: [CALLS] ->[Api.ApiModule.PostApi]
*
* @INVARIANT: Must prevent double-submission while loading.
* @INVARIANT: Loading state must always terminate (no infinite spinner).
@@ -48,7 +48,7 @@
let { plugin_id = "", params = {} } = $props();
let isLoading = $state(false);
// [DEF:spawnTask:Function]
// [DEF:Std.Ai.SpawnTask:Function]
/**
* @PURPOSE: Execute task creation request and emit user feedback.
* @PRE: plugin_id is resolved and request params are serializable.
@@ -75,7 +75,7 @@
isLoading = false;
}
}
// [/DEF:spawnTask:Function]
// [/DEF:Std.Ai.SpawnTask:Function]
</script>
<button
@@ -89,4 +89,4 @@
{/if}
<span>{$t.actions.start_task}</span>
</button>
<!-- [/DEF:FrontendComponentShot:Component] -->
<!-- [/DEF:Std.Ai.FrontendComponentShot:Component] -->

View File

@@ -1,9 +1,9 @@
# [DEF:PluginExampleShot:Module]
# [DEF:Std.Ai.PluginExampleShot:Module]
# @COMPLEXITY: 3
# @SEMANTICS: Plugin, Core, Extension
# @PURPOSE: Reference implementation of a plugin following GRACE standards.
# @LAYER: Domain (Business Logic)
# @RELATION: [INHERITS] ->[PluginBase]
# @RELATION: [INHERITS] ->[Core.PluginBase]
from typing import Dict, Any, Optional
from ..core.plugin_base import PluginBase
@@ -11,15 +11,15 @@ from ..core.task_manager.context import TaskContext
# GRACE: Обязательный импорт семантического логгера
from ..core.logger import logger, belief_scope
# [DEF:ExamplePlugin:Class]
# [DEF:Std.Ai.ExamplePlugin:Class]
# @PURPOSE: A sample plugin to demonstrate execution context and logging.
# @RELATION: [INHERITS] ->[PluginBase]
# @RELATION: [INHERITS] ->[Core.PluginBase]
class ExamplePlugin(PluginBase):
@property
def id(self) -> str:
return "example-plugin"
#[DEF:get_schema:Function]
#[DEF:Std.Ai.GetSchema:Function]
# @PURPOSE: Defines input validation schema.
def get_schema(self) -> Dict[str, Any]:
return {
@@ -32,9 +32,9 @@ class ExamplePlugin(PluginBase):
},
"required": ["message"],
}
#[/DEF:get_schema:Function]
#[/DEF:Std.Ai.GetSchema:Function]
# [DEF:execute:Function]
# [DEF:Std.Ai.Execute:Function]
# @COMPLEXITY: 4
# @PURPOSE: Core plugin logic with structured logging and scope isolation.
# @RELATION: [BINDS_TO] ->[context.logger]
@@ -69,7 +69,7 @@ class ExamplePlugin(PluginBase):
# GRACE: [REFLECT] - Сверка выхода фолбэка
logger.reflect("Standalone execution finalized")
# [/DEF:execute:Function]
# [/DEF:Std.Ai.Execute:Function]
#[/DEF:ExamplePlugin:Class]
# [/DEF:PluginExampleShot:Module]
#[/DEF:Std.Ai.ExamplePlugin:Class]
# [/DEF:Std.Ai.PluginExampleShot:Module]

View File

@@ -1,4 +1,4 @@
# [DEF:TrivialUtilityShot:Module]
# [DEF:Std.Ai.TrivialUtilityShot:Module]
# @COMPLEXITY: 1
# @PURPOSE: Reference implementation of a zero-overhead utility using implicit Complexity 1.
@@ -6,7 +6,7 @@ import re
from datetime import datetime, timezone
from typing import Optional
# [DEF:slugify:Function]
# [DEF:Std.Ai.Slugify:Function]
# @PURPOSE: Converts a string to a URL-safe slug.
def slugify(text: str) -> str:
if not text:
@@ -14,27 +14,27 @@ def slugify(text: str) -> str:
text = text.lower().strip()
text = re.sub(r'[^\w\s-]', '', text)
return re.sub(r'[-\s]+', '-', text)
# [/DEF:slugify:Function]
# [/DEF:Std.Ai.Slugify:Function]
# [DEF:get_utc_now:Function]
# [DEF:Std.Ai.GetUtcNow:Function]
def get_utc_now() -> datetime:
"""Returns current UTC datetime (purpose is omitted because it's obvious)."""
return datetime.now(timezone.utc)
# [/DEF:get_utc_now:Function]
# [/DEF:Std.Ai.GetUtcNow:Function]
# [DEF:PaginationDTO:Class]
# [DEF:Std.Ai.PaginationDTO:Class]
class PaginationDTO:
# [DEF:__init__:Function]
# [DEF:Std.Ai.Init:Function]
def __init__(self, page: int = 1, size: int = 50):
self.page = max(1, page)
self.size = min(max(1, size), 1000)
# [/DEF:__init__:Function]
# [/DEF:Std.Ai.Init:Function]
# [DEF:offset:Function]
# [DEF:Std.Ai.Offset:Function]
@property
def offset(self) -> int:
return (self.page - 1) * self.size
# [/DEF:offset:Function]
# [/DEF:PaginationDTO:Class]
# [/DEF:Std.Ai.Offset:Function]
# [/DEF:Std.Ai.PaginationDTO:Class]
# [/DEF:TrivialUtilityShot:Module]
# [/DEF:Std.Ai.TrivialUtilityShot:Module]

View File

@@ -1,17 +1,17 @@
# #region AxiomConfig [C:5] [TYPE Block] [SEMANTICS config,axiom,indexing]
# #region Config.Axiom [C:5] [TYPE Block] [SEMANTICS config,axiom,indexing]
# @BRIEF Axiom engine configuration — anchor format, indexing rules, tag schema.
# @RATIONALE Single source of truth for the semantic indexing engine. All descriptions in English per MLA token efficiency. Complexity rules use a global tag catalog rather than per-tier duplication (all tags allowed at all tiers per SSOT protocol).
# #region AnchorConfig [C:3] [TYPE Block] [SEMANTICS config,anchor]
# #region AxiomConfig.AnchorConfig [C:3] [TYPE Block] [SEMANTICS config,anchor]
anchor:
format: region
overrides:
docs/: brace
specs/: brace
syntax: {}
# #endregion AnchorConfig
# #endregion AxiomConfig.AnchorConfig
# #region IndexingConfig [C:3] [TYPE Block] [SEMANTICS config,indexing]
# #region AxiomConfig.IndexingConfig [C:3] [TYPE Block] [SEMANTICS config,indexing]
indexing:
include: []
exclude:
@@ -42,9 +42,9 @@ indexing:
- .opencode/command
- .specify/memory
- .specify/templates
# #endregion IndexingConfig
# #endregion AxiomConfig.IndexingConfig
# #region GlobalTagCatalog [C:5] [TYPE Block] [SEMANTICS config,tags,global]
# #region AxiomConfig.GlobalTagCatalog [C:5] [TYPE Block] [SEMANTICS config,tags,global]
# @BRIEF All recognized @-tags — informational, allowed at any tier (C1-C5) per SSOT protocol.
# @INVARIANT Every tag in this catalog has a definition. No tag is forbidden at any tier.
# @RATIONALE Per-tier duplication eliminated — tiers are descriptive, not gatekeeping.
@@ -100,9 +100,9 @@ global_tags:
- INVARIANT_VIOLATION
- VALIDATION
- TEST_DATA
# #endregion GlobalTagCatalog
# #endregion AxiomConfig.GlobalTagCatalog
# #region TagSchema [C:5] [TYPE Block] [SEMANTICS config,tags,schema]
# #region AxiomConfig.TagSchema [C:5] [TYPE Block] [SEMANTICS config,tags,schema]
tags:
C:
type: string
@@ -376,9 +376,9 @@ tags:
multiline: false
description: 'Public API surface — which classes/functions are entry points.'
orthogonal: true
# #endregion TagSchema
# #endregion AxiomConfig.TagSchema
# #region InfrastructureConfig [C:2] [TYPE Block] [SEMANTICS config,embedding,http]
# #region AxiomConfig.InfrastructureConfig [C:2] [TYPE Block] [SEMANTICS config,embedding,http]
embedding: null
http_api:
http_enabled: false
@@ -389,9 +389,9 @@ doc_mode: null
doc_tag_mapping: null
doc_stripped_output: null
doc_symbol_types: null
# #endregion InfrastructureConfig
# #endregion AxiomConfig.InfrastructureConfig
# #region ComplexityRules [C:2] [TYPE Block] [SEMANTICS config,complexity,rules]
# #region AxiomConfig.ComplexityRules [C:2] [TYPE Block] [SEMANTICS config,complexity,rules]
# @BRIEF Per-tier tag requirements from GRACE-Poly SSOT. All tags allowed everywhere,
# but C4+ require formal contract annotations.
complexity_rules:
@@ -400,15 +400,15 @@ complexity_rules:
"5":
required: [PRE, POST, SIDE_EFFECT, DATA_CONTRACT, INVARIANT]
# #endregion ComplexityRules
# #endregion AxiomConfig.ComplexityRules
# #region TierThresholds [C:2] [TYPE Block] [SEMANTICS config,tiers,thresholds]
# #region AxiomConfig.TierThresholds [C:2] [TYPE Block] [SEMANTICS config,tiers,thresholds]
tier_thresholds:
TIER_1: 1
TIER_2: 2
TIER_3: 3
TIER_4: 4
TIER_5: 5
# #endregion TierThresholds
# #endregion AxiomConfig.TierThresholds
# #endregion AxiomConfig
# #endregion Config.Axiom

View File

@@ -198,6 +198,7 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
# @BRIEF One-line purpose.
# @RELATION DEPENDS_ON -> [DependencyService]
# @RELATION DEPENDS_ON -> [DTO:RequestSchema]
# #endregion Domain.Resource.Action
```
**Full header for C4/C5 orchestration & cross-stack functions:**
@@ -215,6 +216,7 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
# @RATIONALE Why this implementation approach.
# @REJECTED What alternative was considered and forbidden.
# @TEST_EDGE: scenario_name -> Expected failure behavior.
# #endregion Domain.Resource.Action
```
**Screen Model actions (Svelte `.svelte.ts`):**
@@ -227,6 +229,7 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
// @SIDE_EFFECT API call, store mutation, model state update.
// @RELATION CALLS -> [apiClient]
// @TEST_EDGE: network_failure -> ScreenState = "error"
// #endregion ScreenModel.actionName
```
**Rules:**
@@ -264,7 +267,7 @@ specs/<feature>/fixtures/
**`manifest.md` — fixture index with GRACE contracts:**
```markdown
#region FixtureManifest [C:3] [TYPE ADR] [SEMANTICS test,fixture,[DOMAIN]]
#region Example.Fixturemanifest [C:3] [TYPE ADR] [SEMANTICS test,fixture,[DOMAIN]]
@defgroup Fixtures Canonical test fixtures for [FEATURE].
## @{ Fixture FX_Auth.Login.Valid [C:2] [TYPE Block] [SEMANTICS test,auth,fixture]
@@ -287,6 +290,10 @@ specs/<feature>/fixtures/
@TEST_INVARIANT: env_reset_selection -> VERIFIED_BY: [Test.Migration.Model]
@TEST_FIXTURE: env_reset -> fixtures/model/migration_env_reset.json
## @} Fixture FX_Migration.EnvReset
## @} FX_Migration.EnvReset
## @} FX_Auth.Login.MissingPassword
## @} FX_Auth.Login.Valid
# #endregion Example.Fixturemanifest
```
**JSON fixture format:**
@@ -341,7 +348,7 @@ Generate `quickstart.md` using real repository verification paths:
If UX contracts exist (`contracts/ux/` was generated by `/speckit.ux`), generate `traceability.md` — a requirements traceability matrix (RTM) mapping every user story through its implementation chain:
```markdown
#region Traceability [C:3] [TYPE ADR] [SEMANTICS traceability,rtm,[DOMAIN]]
#region Std.Kilo.Traceability [C:3] [TYPE ADR] [SEMANTICS traceability,rtm,[DOMAIN]]
@defgroup Trace Matrix Requirements → Model → API → Task → Test for [FEATURE].
## Traceability Matrix
@@ -358,7 +365,7 @@ If UX contracts exist (`contracts/ux/` was generated by `/speckit.ux`), generate
| `GET /api/dashboards` | FX_Dashboards.Hub.* | Test.Dashboards.Hub | /dashboards, /migration |
| `Dashboards.Hub` model | FX_Dashboards.EnvReset | Test.Dashboards.Hub | /dashboards |
#endregion Traceability
#endregion Std.Kilo.Traceability
```
**Generation rules:**

View File

@@ -200,7 +200,7 @@ After all questions are answered, create TWO artifacts:
**`contracts/ux/alternatives.md`** — all options considered, BEFORE final choice:
```markdown
#region UxAlternatives [C:3] [TYPE ADR] [SEMANTICS ux,alternatives,[DOMAIN]]
#region Std.Kilo.UxAlternatives [C:3] [TYPE ADR] [SEMANTICS ux,alternatives,[DOMAIN]]
@defgroup Ux Design alternatives explored for [FEATURE].
## Screen: [Name]
@@ -225,13 +225,13 @@ After all questions are answered, create TWO artifacts:
- ❌ Rejected: Confirm dialog — extra click on every action, annoying at scale
- ❌ Rejected: No confirmation — dangerous for delete/migrate
#endregion UxAlternatives
#endregion Std.Kilo.UxAlternatives
```
**`contracts/ux/decisions.md`** — only the final choices:
```markdown
#region UxDecisions [C:3] [TYPE ADR] [SEMANTICS ux,decisions,[DOMAIN]]
#region Std.Kilo.UxDecisions [C:3] [TYPE ADR] [SEMANTICS ux,decisions,[DOMAIN]]
@defgroup Ux Final UX design decisions for [FEATURE].
## Screen: [Name]
@@ -240,7 +240,7 @@ After all questions are answered, create TWO artifacts:
- Data: Paginated (20/page) + search
- Feedback: Undo toast (5s) for destructive actions
#endregion UxDecisions
#endregion Std.Kilo.UxDecisions
```
**Rule:** `alternatives.md` shows the DESIGN SPACE — agent can see WHY each path was rejected. `decisions.md` is the compact reference for `/speckit.plan`.

View File

@@ -3,7 +3,7 @@ name: molecular-cot-logging
description: Structured logging protocol for agent-driven development, based on molecular Long CoT bonds (REASON/REFLECT/EXPLORE). Replaces legacy Entry/Exit/Coherence markers. Python + Svelte.
---
#region MolecularCoTLogging [C:5] [TYPE Skill] [SEMANTICS reasoning,runtime,logging,agentic]
#region Std.Kilo.MolecularCoTLogging [C:5] [TYPE Skill] [SEMANTICS reasoning,runtime,logging,agentic]
@BRIEF Structured logging protocol for agent-driven development, based on molecular Long CoT bonds (Deep-Reasoning, Self-Reflection, Self-Exploration). Replaces legacy Entry/Exit/Coherence markers.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DISPATCHES -> [Std.Semantics.Python]
@@ -303,4 +303,4 @@ for line in sys.stdin:
| Logging raw passwords or tokens in `payload` | Always sanitise sensitive data |
| Spread markers across multiple modules without trace_id | Always propagate `trace_id` |
#endregion MolecularCoTLogging
#endregion Std.Kilo.MolecularCoTLogging

View File

@@ -3,10 +3,10 @@ name: semantics-frontend
description: Core protocol for Svelte 5 (Runes) Components, UX State Machines, and Visual-Interactive Validation.
---
# [DEF:Std:Semantics:Frontend]
# [DEF:Std.Kilo.Std:Semantics:Frontend]
# @COMPLEXITY: 5
# @PURPOSE: Canonical GRACE-Poly protocol for Svelte 5 (Runes) Components, UX State Machines, and Project UI Architecture.
# @RELATION: DEPENDS_ON ->[Std:Semantics:Core]
# @RELATION: DEPENDS_ON ->[Std.Kilo.Std:Semantics:Core]
# @INVARIANT: Frontend components MUST be verifiable by an automated GUI Judge Agent (e.g., Playwright).
# @INVARIANT: Use Tailwind CSS exclusively. Native `fetch` is forbidden.
@@ -55,7 +55,8 @@ Frontend logging bridges the gap between your logic and the Judge Agent's vision
You MUST strictly adhere to this AST boundary format:
```html
<!-- [DEF:ComponentName:Component] -->
# [/DEF:Std.Kilo.Std:Semantics:Frontend]
<!-- [DEF:Std.Kilo.ComponentName:Component] -->
<script>
/**
* @COMPLEXITY: [1-5]
@@ -104,4 +105,4 @@ You MUST strictly adhere to this AST boundary format:
{$t('actions.start')}
</button>
</div>
<!--[/DEF:ComponentName:Component] -->
<!--[/DEF:Std.Kilo.ComponentName:Component] -->

View File

@@ -3,10 +3,10 @@ name: semantics-belief
description: Core protocol for Thread-Local Belief State, Runtime Chain-of-Thought (CoT), and Interleaved Thinking in Python.
---
# [DEF:Std:Semantics:Belief]
# [DEF:Std.Kilo.Std:Semantics:Belief]
# @COMPLEXITY: 5
# @PURPOSE: Core protocol for Thread-Local Belief State, Runtime Chain-of-Thought (CoT), and Interleaved Thinking in Python.
# @RELATION: DEPENDS_ON -> [Std:Semantics:Core]
# @RELATION: DEPENDS_ON -> [Std.Kilo.Std:Semantics:Core]
# @INVARIANT: Implementation of C4/C5 complexity nodes MUST emit reasoning via semantic logger methods before mutating state or returning.
## 0. INTERLEAVED THINKING (GLM-5 PARADIGM)
@@ -53,5 +53,5 @@ If your execution path triggers a `logger.explore()` due to a broken assumption
**YOU MUST ASCEND TO THE `[DEF]` HEADER AND DOCUMENT IT.**
You must add `@RATIONALE: [Why you did this]` and `@REJECTED:[The path that failed during explore()]`.
Failure to link a runtime `explore` to a static `@REJECTED` tag is a fatal protocol violation that causes amnesia for future agents.
# [/DEF:Std:Semantics:Belief]
# [/DEF:Std.Kilo.Std:Semantics:Belief]
**[SYSTEM: END OF BELIEF DIRECTIVE. ENFORCE STRICT RUNTIME CoT.]**

View File

@@ -97,18 +97,18 @@ Not all GRACE tags are equal in the model's training data. Understanding which t
### Legacy — DEF (permanently recognized)
```python
// [DEF:ContractId:Type]
// [DEF:Std.Kilo.ContractId:Type]
// @TAG: value
<code>
// [/DEF:ContractId:Type]
// [/DEF:Std.Kilo.ContractId:Type]
```
### Doc — Brace (Markdown, specs, ADRs)
```
## @{ ContractId [C:N] [TYPE TypeName]
## @{ Std.Kilo.ContractId [C:N] [TYPE TypeName]
@BRIEF Description
...
## @} ContractId
## @} Std.Kilo.ContractId
```
**Allowed Types:** Module, Function, Class, Component, Model, Block, ADR, Tombstone, Skill, Agent.
@@ -260,6 +260,7 @@ The opening anchor MUST pack maximum signal into one line:
```
#region Domain.Sub.ContractId [C:N] [TYPE TypeName] [SEMANTICS tag1,tag2,tag3]
# #endregion Domain.Sub.ContractId
```
- ID, complexity, type, and semantic tags on ONE line → survives CSA 4× pooling as a single KV record.
@@ -303,6 +304,7 @@ Example — both mechanisms reinforce each other:
#region Core.Auth.Login [C:4] [TYPE Function] [SEMANTICS auth,login,token]
# @ingroup Auth
# @BRIEF Authenticate user by credentials.
# #endregion Core.Auth.Login
```
**Rule:** Identical domain = identical primary keyword in `[SEMANTICS ...]` AND identical `@ingroup Domain`. They target different compression layers (CSA vs HCA) and don't conflict — the keyword repetition amplifies the DSA score.

View File

@@ -7,7 +7,7 @@ description: Python-specific GRACE-Poly protocol: few-shot complexity examples,
@BRIEF Python-specific HOW: few-shot complexity examples, belief runtime patterns, module decomposition, and FastAPI/SQLAlchemy conventions for the GRACE-Poly protocol in superset-tools.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DEPENDS_ON -> [Std.Semantics.Contracts]
@RELATION DISPATCHES -> [MolecularCoTLogging]
@RELATION DISPATCHES -> [Std.Kilo.MolecularCoTLogging]
@RESTRICTION EXAMPLES ONLY — this file provides language-specific code patterns. All protocol rules (tier definitions, tag catalog, anchor syntax) are defined exclusively in `semantics-core`. This file MUST NOT redefine or contradict any rule from `semantics-core`.
@RATIONALE Python's async/await model, FastAPI dependency injection, and SQLAlchemy session management create unique failure modes for Transformer agents: (1) async/await boundary confusion — agents write sync code in async contexts or forget `await` on ORM calls, producing silent no-ops; (2) dependency injection blindness — FastAPI's `Depends()` creates implicit call graphs that the agent's attention cannot trace without explicit @RELATION edges; (3) session lifecycle drift — SQLAlchemy sessions have strict boundaries that agents violate by passing detached objects across function calls. Concrete examples at each complexity tier act as few-shot anchors that override the agent's pre-trained (and often wrong) Python patterns.
@REJECTED Generic Python patterns without GRACE anchors were rejected — agents produce working code that violates module size limits (INV_7), omits belief runtime markers, and creates orphan contracts invisible to the semantic index. Relying on the agent's pre-trained FastAPI/SQLAlchemy knowledge without project-specific examples was rejected — superset-tools has specific conventions (trace_id propagation, plugin architecture, WebSocket logging) that general training data cannot capture.

View File

@@ -6,7 +6,7 @@ description: Svelte 5 (Runes) protocol for superset-tools: UX State Machines, Ta
#region Std.Semantics.Svelte [C:5] [TYPE Skill] [SEMANTICS frontend,svelte,ui,ux,tailwind]
@BRIEF HOW to build Svelte 5 (Runes) Components for superset-tools with UX State Machines, Tailwind CSS, store topology, and visual-interactive validation.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DEPENDS_ON -> [MolecularCoTLogging]
@RELATION DEPENDS_ON -> [Std.Kilo.MolecularCoTLogging]
@RELATION DISPATCHES -> [Std.Semantics.Testing]
@RESTRICTION EXAMPLES ONLY — this file provides language-specific code patterns. All protocol rules (tier definitions, tag catalog, anchor syntax) are defined exclusively in `semantics-core`. UX contract tags are defined here as examples; the tag catalog lives in `semantics-core` §III. This file MUST NOT redefine or contradict any rule from `semantics-core`.
@RATIONALE Svelte 5 runes ($state, $derived, $effect, $props) chosen for reactive precision and native compiler optimisations over Svelte 4 legacy reactivity ($:). Tailwind CSS selected for zero-runtime utility-first styling and rapid visual validation via chrome-devtools MCP. FSM-based UX contracts (@UX_STATE, @UX_FEEDBACK, @UX_RECOVERY) chosen to create verifiable state-transition tests that the browser Judge Agent can execute deterministically. superset-tools internal API wrappers (fetchApi/requestApi) chosen over native fetch to enforce auth, error normalisation, and trace_id propagation. Model-first architecture chosen because event-handler spaghetti is the #1 Transformer failure mode in UI code: the agent scatters logic across onclick/onchange in 5 files — KV-cache cannot hold cross-component relationships, creating invisible coupling that breaks silently.
@@ -309,7 +309,7 @@ Frontend logging uses `log()` from `$lib/cot-logger` per **MolecularCoTLogging**
Region format for HTML/Svelte comments:
```html
<!-- #region MigrationTaskCard [C:3] [TYPE Component] [SEMANTICS ui,migration,task] -->
<!-- #region Std.Semantics.MigrationTaskCard [C:3] [TYPE Component] [SEMANTICS ui,migration,task] -->
<!-- @BRIEF Card displaying a migration task with status, progress, and action buttons. -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [StatusBadge] -->
@@ -410,7 +410,7 @@ Region format for HTML/Svelte comments:
</Button>
</div>
</div>
<!-- #endregion MigrationTaskCard -->
<!-- #endregion Std.Semantics.MigrationTaskCard -->
```
## VII. SS-TOOLS DESIGN TOKEN CANON & COMPONENT REUSE

View File

@@ -90,7 +90,7 @@ from unittest.mock import AsyncMock, patch
class TestDashboardMigration:
"""Verify migrate_dashboard @POST guarantees."""
# #region test_migrate_dashboard_success [C:2] [TYPE Function]
# #region Std.Semantics.TestMigrateDashboardSuccess [C:2] [TYPE Function]
# @BRIEF Happy path: valid dashboard with complete db mapping.
@pytest.mark.asyncio
async def test_migrate_dashboard_success(self):
@@ -98,8 +98,9 @@ class TestDashboardMigration:
expected = {"id": "dash_1", "status": "imported"}
# ... test implementation
pass
# #endregion test_migrate_dashboard_success
# #endregion Std.Semantics.TestMigrateDashboardSuccess
# #endregion TestDashboardMigration
# #endregion Test.Migration.RunTask
```
### Running tests

View File

@@ -123,7 +123,7 @@ For Svelte components with `@UX_STATE`, `@UX_FEEDBACK`, `@UX_RECOVERY` tags:
**UX Test Template:**
```javascript
// [DEF:ComponentUXTests:Module]
// [DEF:Example.Componentuxtests:Module]
// @C: 3
// @RELATION: VERIFIES -> ../Component.svelte
// @PURPOSE: Test UX states and transitions
@@ -139,6 +139,7 @@ describe('Component UX States', () => {
it('should allow retry on error', async () => { ... });
});
// [/DEF:__tests__/test_Component:Module]
// [/DEF:Example.Componentuxtests:Module]
```
### 5. Test Documentation

View File

@@ -198,6 +198,7 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
# @BRIEF One-line purpose.
# @RELATION DEPENDS_ON -> [DependencyService]
# @RELATION DEPENDS_ON -> [DTO:RequestSchema]
# #endregion Domain.Resource.Action
```
**Full header for C4/C5 orchestration & cross-stack functions:**
@@ -215,6 +216,7 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
# @RATIONALE Why this implementation approach.
# @REJECTED What alternative was considered and forbidden.
# @TEST_EDGE: scenario_name -> Expected failure behavior.
# #endregion Domain.Resource.Action
```
**Screen Model actions (Svelte `.svelte.ts`):**
@@ -227,6 +229,7 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
// @SIDE_EFFECT API call, store mutation, model state update.
// @RELATION CALLS -> [apiClient]
// @TEST_EDGE: network_failure -> ScreenState = "error"
// #endregion ScreenModel.actionName
```
**Rules:**
@@ -264,7 +267,7 @@ specs/<feature>/fixtures/
**`manifest.md` — fixture index with GRACE contracts:**
```markdown
#region FixtureManifest [C:3] [TYPE ADR] [SEMANTICS test,fixture,[DOMAIN]]
#region Example.Fixturemanifest [C:3] [TYPE ADR] [SEMANTICS test,fixture,[DOMAIN]]
@defgroup Fixtures Canonical test fixtures for [FEATURE].
## @{ Fixture FX_Auth.Login.Valid [C:2] [TYPE Block] [SEMANTICS test,auth,fixture]
@@ -287,6 +290,10 @@ specs/<feature>/fixtures/
@TEST_INVARIANT: env_reset_selection -> VERIFIED_BY: [Test.Migration.Model]
@TEST_FIXTURE: env_reset -> fixtures/model/migration_env_reset.json
## @} Fixture FX_Migration.EnvReset
## @} FX_Migration.EnvReset
## @} FX_Auth.Login.MissingPassword
## @} FX_Auth.Login.Valid
# #endregion Example.Fixturemanifest
```
**JSON fixture format:**
@@ -358,7 +365,7 @@ docker compose up --build
If UX contracts exist (`contracts/ux/` was generated by `/speckit.ux`), generate `traceability.md` — a requirements traceability matrix (RTM) mapping every user story through its implementation chain:
```markdown
#region Traceability [C:3] [TYPE ADR] [SEMANTICS traceability,rtm,[DOMAIN]]
#region Std.Opencode.Traceability [C:3] [TYPE ADR] [SEMANTICS traceability,rtm,[DOMAIN]]
@defgroup Trace Matrix Requirements → Model → API → Task → Test for [FEATURE].
## Traceability Matrix
@@ -375,7 +382,7 @@ If UX contracts exist (`contracts/ux/` was generated by `/speckit.ux`), generate
| `GET /api/dashboards` | FX_Dashboards.Hub.* | Test.Dashboards.Hub | /dashboards, /migration |
| `Dashboards.Hub` model | FX_Dashboards.EnvReset | Test.Dashboards.Hub | /dashboards |
#endregion Traceability
#endregion Std.Opencode.Traceability
```
**Generation rules:**

View File

@@ -200,7 +200,7 @@ After all questions are answered, create TWO artifacts:
**`contracts/ux/alternatives.md`** — all options considered, BEFORE final choice:
```markdown
#region UxAlternatives [C:3] [TYPE ADR] [SEMANTICS ux,alternatives,[DOMAIN]]
#region Std.Opencode.UxAlternatives [C:3] [TYPE ADR] [SEMANTICS ux,alternatives,[DOMAIN]]
@defgroup Ux Design alternatives explored for [FEATURE].
## Screen: [Name]
@@ -225,13 +225,13 @@ After all questions are answered, create TWO artifacts:
- ❌ Rejected: Confirm dialog — extra click on every action, annoying at scale
- ❌ Rejected: No confirmation — dangerous for delete/migrate
#endregion UxAlternatives
#endregion Std.Opencode.UxAlternatives
```
**`contracts/ux/decisions.md`** — only the final choices:
```markdown
#region UxDecisions [C:3] [TYPE ADR] [SEMANTICS ux,decisions,[DOMAIN]]
#region Std.Opencode.UxDecisions [C:3] [TYPE ADR] [SEMANTICS ux,decisions,[DOMAIN]]
@defgroup Ux Final UX design decisions for [FEATURE].
## Screen: [Name]
@@ -240,7 +240,7 @@ After all questions are answered, create TWO artifacts:
- Data: Paginated (20/page) + search
- Feedback: Undo toast (5s) for destructive actions
#endregion UxDecisions
#endregion Std.Opencode.UxDecisions
```
**Rule:** `alternatives.md` shows the DESIGN SPACE — agent can see WHY each path was rejected. `decisions.md` is the compact reference for `/speckit.plan`.

View File

@@ -3,7 +3,7 @@ name: molecular-cot-logging
description: Structured logging protocol for agent-driven development, based on molecular Long CoT bonds (REASON/REFLECT/EXPLORE). Replaces legacy Entry/Exit/Coherence markers. Python + Svelte.
---
#region MolecularCoTLogging [C:5] [TYPE Skill] [SEMANTICS reasoning,runtime,logging,agentic]
#region Std.Opencode.MolecularCoTLogging [C:5] [TYPE Skill] [SEMANTICS reasoning,runtime,logging,agentic]
@BRIEF Structured logging protocol for agent-driven development, based on molecular Long CoT bonds (Deep-Reasoning, Self-Reflection, Self-Exploration). Replaces legacy Entry/Exit/Coherence markers.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DISPATCHES -> [Std.Semantics.Python]
@@ -303,4 +303,4 @@ for line in sys.stdin:
| Logging raw passwords or tokens in `payload` | Always sanitise sensitive data |
| Spread markers across multiple modules without trace_id | Always propagate `trace_id` |
#endregion MolecularCoTLogging
#endregion Std.Opencode.MolecularCoTLogging

View File

@@ -3,10 +3,10 @@ name: semantics-frontend
description: Core protocol for Svelte 5 (Runes) Components, UX State Machines, and Visual-Interactive Validation.
---
# [DEF:Std:Semantics:Frontend]
# [DEF:Std.Opencode.Std:Semantics:Frontend]
# @COMPLEXITY: 5
# @PURPOSE: Canonical GRACE-Poly protocol for Svelte 5 (Runes) Components, UX State Machines, and Project UI Architecture.
# @RELATION: DEPENDS_ON ->[Std:Semantics:Core]
# @RELATION: DEPENDS_ON ->[Std.Opencode.Std:Semantics:Core]
# @INVARIANT: Frontend components MUST be verifiable by an automated GUI Judge Agent (e.g., Playwright).
# @INVARIANT: Use Tailwind CSS exclusively. Native `fetch` is forbidden.
@@ -55,7 +55,8 @@ Frontend logging bridges the gap between your logic and the Judge Agent's vision
You MUST strictly adhere to this AST boundary format:
```html
<!-- [DEF:ComponentName:Component] -->
# [/DEF:Std.Opencode.Std:Semantics:Frontend]
<!-- [DEF:Std.Opencode.ComponentName:Component] -->
<script>
/**
* @COMPLEXITY: [1-5]
@@ -104,4 +105,4 @@ You MUST strictly adhere to this AST boundary format:
{$t('actions.start')}
</button>
</div>
<!--[/DEF:ComponentName:Component] -->
<!--[/DEF:Std.Opencode.ComponentName:Component] -->

View File

@@ -3,10 +3,10 @@ name: semantics-belief
description: Core protocol for Thread-Local Belief State, Runtime Chain-of-Thought (CoT), and Interleaved Thinking in Python.
---
# [DEF:Std:Semantics:Belief]
# [DEF:Std.Opencode.Std:Semantics:Belief]
# @COMPLEXITY: 5
# @PURPOSE: Core protocol for Thread-Local Belief State, Runtime Chain-of-Thought (CoT), and Interleaved Thinking in Python.
# @RELATION: DEPENDS_ON -> [Std:Semantics:Core]
# @RELATION: DEPENDS_ON -> [Std.Opencode.Std:Semantics:Core]
# @INVARIANT: Implementation of C4/C5 complexity nodes MUST emit reasoning via semantic logger methods before mutating state or returning.
## 0. INTERLEAVED THINKING (GLM-5 PARADIGM)
@@ -53,5 +53,5 @@ If your execution path triggers a `logger.explore()` due to a broken assumption
**YOU MUST ASCEND TO THE `[DEF]` HEADER AND DOCUMENT IT.**
You must add `@RATIONALE: [Why you did this]` and `@REJECTED:[The path that failed during explore()]`.
Failure to link a runtime `explore` to a static `@REJECTED` tag is a fatal protocol violation that causes amnesia for future agents.
# [/DEF:Std:Semantics:Belief]
# [/DEF:Std.Opencode.Std:Semantics:Belief]
**[SYSTEM: END OF BELIEF DIRECTIVE. ENFORCE STRICT RUNTIME CoT.]**

View File

@@ -97,18 +97,18 @@ Not all GRACE tags are equal in the model's training data. Understanding which t
### Legacy — DEF (permanently recognized)
```python
// [DEF:ContractId:Type]
// [DEF:Std.Opencode.ContractId:Type]
// @TAG: value
<code>
// [/DEF:ContractId:Type]
// [/DEF:Std.Opencode.ContractId:Type]
```
### Doc — Brace (Markdown, specs, ADRs)
```
## @{ ContractId [C:N] [TYPE TypeName]
## @{ Std.Opencode.ContractId [C:N] [TYPE TypeName]
@BRIEF Description
...
## @} ContractId
## @} Std.Opencode.ContractId
```
**Allowed Types:** Module, Function, Class, Component, Model, Block, ADR, Tombstone, Skill, Agent.
@@ -260,6 +260,7 @@ The opening anchor MUST pack maximum signal into one line:
```
#region Domain.Sub.ContractId [C:N] [TYPE TypeName] [SEMANTICS tag1,tag2,tag3]
# #endregion Domain.Sub.ContractId
```
- ID, complexity, type, and semantic tags on ONE line → survives CSA 4× pooling as a single KV record.
@@ -303,6 +304,7 @@ Example — both mechanisms reinforce each other:
#region Core.Auth.Login [C:4] [TYPE Function] [SEMANTICS auth,login,token]
# @ingroup Auth
# @BRIEF Authenticate user by credentials.
# #endregion Core.Auth.Login
```
**Rule:** Identical domain = identical primary keyword in `[SEMANTICS ...]` AND identical `@ingroup Domain`. They target different compression layers (CSA vs HCA) and don't conflict — the keyword repetition amplifies the DSA score.

View File

@@ -7,7 +7,7 @@ description: Python-specific GRACE-Poly protocol: few-shot complexity examples,
@BRIEF Python-specific HOW: few-shot complexity examples, belief runtime patterns, module decomposition, and FastAPI/SQLAlchemy conventions for the GRACE-Poly protocol in superset-tools.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DEPENDS_ON -> [Std.Semantics.Contracts]
@RELATION DISPATCHES -> [MolecularCoTLogging]
@RELATION DISPATCHES -> [Std.Opencode.MolecularCoTLogging]
@RESTRICTION EXAMPLES ONLY — this file provides language-specific code patterns. All protocol rules (tier definitions, tag catalog, anchor syntax) are defined exclusively in `semantics-core`. This file MUST NOT redefine or contradict any rule from `semantics-core`.
@RATIONALE Python's async/await model, FastAPI dependency injection, and SQLAlchemy session management create unique failure modes for Transformer agents: (1) async/await boundary confusion — agents write sync code in async contexts or forget `await` on ORM calls, producing silent no-ops; (2) dependency injection blindness — FastAPI's `Depends()` creates implicit call graphs that the agent's attention cannot trace without explicit @RELATION edges; (3) session lifecycle drift — SQLAlchemy sessions have strict boundaries that agents violate by passing detached objects across function calls. Concrete examples at each complexity tier act as few-shot anchors that override the agent's pre-trained (and often wrong) Python patterns.
@REJECTED Generic Python patterns without GRACE anchors were rejected — agents produce working code that violates module size limits (INV_7), omits belief runtime markers, and creates orphan contracts invisible to the semantic index. Relying on the agent's pre-trained FastAPI/SQLAlchemy knowledge without project-specific examples was rejected — superset-tools has specific conventions (trace_id propagation, plugin architecture, WebSocket logging) that general training data cannot capture.

View File

@@ -6,7 +6,7 @@ description: Svelte 5 (Runes) protocol for superset-tools: UX State Machines, Ta
#region Std.Semantics.Svelte [C:5] [TYPE Skill] [SEMANTICS frontend,svelte,ui,ux,tailwind]
@BRIEF HOW to build Svelte 5 (Runes) Components for superset-tools with UX State Machines, Tailwind CSS, store topology, and visual-interactive validation.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DEPENDS_ON -> [MolecularCoTLogging]
@RELATION DEPENDS_ON -> [Std.Opencode.MolecularCoTLogging]
@RELATION DISPATCHES -> [Std.Semantics.Testing]
@RESTRICTION EXAMPLES ONLY — this file provides language-specific code patterns. All protocol rules (tier definitions, tag catalog, anchor syntax) are defined exclusively in `semantics-core`. UX contract tags are defined here as examples; the tag catalog lives in `semantics-core` §III. This file MUST NOT redefine or contradict any rule from `semantics-core`.
@RATIONALE Svelte 5 runes ($state, $derived, $effect, $props) chosen for reactive precision and native compiler optimisations over Svelte 4 legacy reactivity ($:). Tailwind CSS selected for zero-runtime utility-first styling and rapid visual validation via chrome-devtools MCP. FSM-based UX contracts (@UX_STATE, @UX_FEEDBACK, @UX_RECOVERY) chosen to create verifiable state-transition tests that the browser Judge Agent can execute deterministically. superset-tools internal API wrappers (fetchApi/requestApi) chosen over native fetch to enforce auth, error normalisation, and trace_id propagation. Model-first architecture chosen because event-handler spaghetti is the #1 Transformer failure mode in UI code: the agent scatters logic across onclick/onchange in 5 files — KV-cache cannot hold cross-component relationships, creating invisible coupling that breaks silently.
@@ -309,7 +309,7 @@ Frontend logging uses `log()` from `$lib/cot-logger` per **MolecularCoTLogging**
Region format for HTML/Svelte comments:
```html
<!-- #region MigrationTaskCard [C:3] [TYPE Component] [SEMANTICS ui,migration,task] -->
<!-- #region Std.Semantics.MigrationTaskCard [C:3] [TYPE Component] [SEMANTICS ui,migration,task] -->
<!-- @BRIEF Card displaying a migration task with status, progress, and action buttons. -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [StatusBadge] -->
@@ -410,7 +410,7 @@ Region format for HTML/Svelte comments:
</Button>
</div>
</div>
<!-- #endregion MigrationTaskCard -->
<!-- #endregion Std.Semantics.MigrationTaskCard -->
```
## VII. SS-TOOLS DESIGN TOKEN CANON & COMPONENT REUSE

View File

@@ -90,7 +90,7 @@ from unittest.mock import AsyncMock, patch
class TestDashboardMigration:
"""Verify migrate_dashboard @POST guarantees."""
# #region test_migrate_dashboard_success [C:2] [TYPE Function]
# #region Std.Semantics.TestMigrateDashboardSuccess [C:2] [TYPE Function]
# @BRIEF Happy path: valid dashboard with complete db mapping.
@pytest.mark.asyncio
async def test_migrate_dashboard_success(self):
@@ -98,8 +98,9 @@ class TestDashboardMigration:
expected = {"id": "dash_1", "status": "imported"}
# ... test implementation
pass
# #endregion test_migrate_dashboard_success
# #endregion Std.Semantics.TestMigrateDashboardSuccess
# #endregion TestDashboardMigration
# #endregion Test.Migration.RunTask
```
### Running tests

View File

@@ -1,4 +1,4 @@
#region FeatureSpec [C:3] [TYPE ADR] [SEMANTICS spec,requirements,feature]
#region Std.Specify.FeatureSpec [C:3] [TYPE ADR] [SEMANTICS spec,requirements,feature]
@BRIEF Feature specification — WHAT the user needs and WHY. Implementation-free. Survives HCA 128× via @SEMANTICS grouping.
## Navigation (DSA Indexer keywords)
@@ -75,4 +75,4 @@ All stories share `@SEMANTICS` domain keywords from the feature header.
- **SC-002**: [Measurable metric]
- **SC-003**: [User-facing metric]
#endregion FeatureSpec
#endregion Std.Specify.FeatureSpec

View File

@@ -1,4 +1,4 @@
#region UxReference [C:3] [TYPE ADR] [SEMANTICS ux, reference, [DOMAIN]]
#region Std.Specify.UxReference [C:3] [TYPE ADR] [SEMANTICS ux, reference, [DOMAIN]]
@BRIEF UX interaction reference — persona, flows, states, and recovery paths. Drives `@UX_*` contract tags in Phase 1.
**Feature Branch**: `[###-feature-name]`
@@ -75,4 +75,4 @@ $ command --flag value
* **Style**: [e.g. Concise, Technical, Friendly, Verbose]
* **Terminology**: [e.g. Use "Repository" not "Repo", "Directory" not "Folder"]
#endregion UxReference
#endregion Std.Specify.UxReference

View File

@@ -1,5 +1,5 @@
# agent/src/ss_tools/agent/__init__.py
# #region AgentChat [C:3] [TYPE Module] [SEMANTICS agent-chat]
# #region Agent.Init.AgentChat [C:3] [TYPE Module] [SEMANTICS agent-chat]
# @defgroup AgentChat LangGraph-based Gradio agent — streaming chat with HITL guardrails.
# @LAYER Application
# @RELATION DISPATCHES -> [AgentChat.Config]
@@ -15,4 +15,4 @@
# @RELATION DISPATCHES -> [AgentChat.Persistence]
# @RELATION DISPATCHES -> [AgentChat.Document.Parser]
# @RELATION DISPATCHES -> [AgentChat.GradioApp]
# #endregion AgentChat
# #endregion Agent.Init.AgentChat

View File

@@ -34,7 +34,7 @@ def mock_logger():
# emit_lifecycle_event — local logging
# ═══════════════════════════════════════════════════════════════════
# #region test_lifecycle_logs_locally [C:2] [TYPE Function] [SEMANTICS test,lifecycle,log,local]
# #region Test.AgentChat.TestLifecycleLogsLocally [C:2] [TYPE Function] [SEMANTICS test,lifecycle,log,local]
# @BRIEF emit_lifecycle_event logs the event via logger.reason.
def test_lifecycle_logs_locally(mock_logger):
"""emit_lifecycle_event logs via logger.reason with correct event_type."""
@@ -57,10 +57,10 @@ def test_lifecycle_logs_locally(mock_logger):
# src should be AgentChat.Lifecycle
extra = call_kwargs[1].get("extra", {})
assert extra.get("src") == "AgentChat.Lifecycle"
# #endregion test_lifecycle_logs_locally
# #endregion Test.AgentChat.TestLifecycleLogsLocally
# #region test_lifecycle_persistence_uses_end_user_identity [C:2] [TYPE Function]
# #region Test.AgentChat.TestLifecyclePersistenceUsesEndUserIdentity [C:2] [TYPE Function]
# @BRIEF The durable audit transport forwards the end-user JWT only in the delegation header.
@pytest.mark.asyncio
async def test_lifecycle_persistence_uses_end_user_identity():
@@ -85,10 +85,10 @@ async def test_lifecycle_persistence_uses_end_user_identity():
headers = client.post.call_args.kwargs["headers"]
assert headers["Authorization"] == "Bearer service.jwt.token"
assert headers["X-User-JWT"] == "user.jwt.token"
# #endregion test_lifecycle_persistence_uses_end_user_identity
# #endregion Test.AgentChat.TestLifecyclePersistenceUsesEndUserIdentity
# #region test_lifecycle_strips_sensitive_fields [C:2] [TYPE Function] [SEMANTICS test,lifecycle,payload,whitelist]
# #region Test.AgentChat.TestLifecycleStripsSensitiveFields [C:2] [TYPE Function] [SEMANTICS test,lifecycle,payload,whitelist]
# @BRIEF emit_lifecycle_event strips sensitive fields from payload before logging.
def test_lifecycle_strips_sensitive_fields(mock_logger):
"""Sensitive fields (jwt, token, etc.) are stripped from the payload."""
@@ -125,7 +125,7 @@ def test_lifecycle_strips_sensitive_fields(mock_logger):
assert "prompt" not in payload
assert "tool_output" not in payload
assert "files" not in payload
# #endregion test_lifecycle_strips_sensitive_fields
# #endregion Test.AgentChat.TestLifecycleStripsSensitiveFields
# #region test_lifecycle_strips_none_values [C:1] [TYPE Function] [SEMANTICS test,lifecycle,payload,none]
@@ -150,7 +150,7 @@ def test_lifecycle_strips_none_values(mock_logger):
# #endregion test_lifecycle_strips_none_values
# #region test_lifecycle_failure_uses_explore [C:2] [TYPE Function] [SEMANTICS test,lifecycle,log,failure]
# #region Test.AgentChat.TestLifecycleFailureUsesExplore [C:2] [TYPE Function] [SEMANTICS test,lifecycle,log,failure]
# @BRIEF Failed lifecycle events carry an EXPLORE bond and retain only safe provider diagnostics.
def test_lifecycle_failure_uses_explore(mock_logger):
from ss_tools.agent.middleware import emit_lifecycle_event
@@ -169,14 +169,14 @@ def test_lifecycle_failure_uses_explore(mock_logger):
assert kwargs["payload"]["provider_host"] == "lite.ai.rusal.com"
assert "api_key" not in kwargs["payload"]
assert kwargs["error"] == "LLM_PROVIDER_UNAVAILABLE"
# #endregion test_lifecycle_failure_uses_explore
# #endregion Test.AgentChat.TestLifecycleFailureUsesExplore
# ═══════════════════════════════════════════════════════════════════
# emit_lifecycle_event — async HTTP persistence
# ═══════════════════════════════════════════════════════════════════
# #region test_lifecycle_http_persist_success [C:3] [TYPE Function] [SEMANTICS test,lifecycle,http,send]
# #region Test.AgentChat.TestLifecycleHttpPersistSuccess [C:3] [TYPE Function] [SEMANTICS test,lifecycle,http,send]
# @BRIEF emit_lifecycle_event POSTs to backend when FASTAPI_URL is set.
@pytest.mark.asyncio
async def test_lifecycle_http_persist_success():
@@ -212,10 +212,10 @@ async def test_lifecycle_http_persist_success():
# Authorization header should be set
headers = call_kwargs[1].get("headers", {})
assert headers.get("Authorization") == "Bearer test-service-jwt"
# #endregion test_lifecycle_http_persist_success
# #endregion Test.AgentChat.TestLifecycleHttpPersistSuccess
# #region test_lifecycle_http_persist_failure_does_not_raise [C:2] [TYPE Function] [SEMANTICS test,lifecycle,http,failure]
# #region Test.AgentChat.TestLifecycleHttpPersistFailureDoesNotRaise [C:2] [TYPE Function] [SEMANTICS test,lifecycle,http,failure]
# @BRIEF Backend HTTP failure is logged as EXPLORE, never raised.
@pytest.mark.asyncio
async def test_lifecycle_http_persist_failure_does_not_raise():
@@ -241,7 +241,7 @@ async def test_lifecycle_http_persist_failure_does_not_raise():
# The important assertion: the function itself doesn't raise
# The HTTP call is fire-and-forget
mock_client.post.assert_called_once()
# #endregion test_lifecycle_http_persist_failure_does_not_raise
# #endregion Test.AgentChat.TestLifecycleHttpPersistFailureDoesNotRaise
# #region test_lifecycle_no_backend_skips_http [C:1] [TYPE Function] [SEMANTICS test,lifecycle,http,skip]
@@ -263,7 +263,7 @@ def test_lifecycle_no_backend_skips_http(mock_logger):
# #endregion test_lifecycle_no_backend_skips_http
# #region test_lifecycle_http_400_logged [C:2] [TYPE Function] [SEMANTICS test,lifecycle,http,rejected]
# #region Test.AgentChat.TestLifecycleHttp400Logged [C:2] [TYPE Function] [SEMANTICS test,lifecycle,http,rejected]
# @BRIEF HTTP 400+ response is logged as EXPLORE.
@pytest.mark.asyncio
async def test_lifecycle_http_400_logged():
@@ -288,10 +288,10 @@ async def test_lifecycle_http_400_logged():
# Should log rejection as EXPLORE
explore_calls = [c for c in mock_log.explore.call_args_list if "rejected" in str(c)]
assert len(explore_calls) >= 0 # best-effort, may race
# #endregion test_lifecycle_http_400_logged
# #endregion Test.AgentChat.TestLifecycleHttp400Logged
# #region test_lifecycle_resources_close [C:2] [TYPE Function] [SEMANTICS test,lifecycle,http,shutdown]
# #region Test.AgentChat.TestLifecycleResourcesClose [C:2] [TYPE Function] [SEMANTICS test,lifecycle,http,shutdown]
# @BRIEF Pending lifecycle writes are drained and the shared client is closed on shutdown.
@pytest.mark.asyncio
async def test_lifecycle_resources_close():
@@ -302,5 +302,5 @@ async def test_lifecycle_resources_close():
await mw.close_lifecycle_resources()
client.aclose.assert_awaited_once()
assert mw._lifecycle_client is None
# #endregion test_lifecycle_resources_close
# #endregion Test.AgentChat.TestLifecycleResourcesClose
# #endregion Test.AgentChat.Lifecycle

View File

@@ -47,7 +47,7 @@ def clear_pending():
# build_confirmation_contract
# ═══════════════════════════════════════════════════════════════════
# #region test_build_confirmation_contract [C:2] [TYPE Class]
# #region Test.AgentChat.TestBuildConfirmationContract [C:2] [TYPE Class]
# @BRIEF Test build_confirmation_contract for all risk levels.
class TestBuildConfirmationContract:
def test_safe_tool_returns_read_contract(self):
@@ -86,14 +86,14 @@ class TestBuildConfirmationContract:
assert c["operation"] == "unknown_action"
assert c["risk"] == "read"
assert c["risk_level"] == "safe"
# #endregion test_build_confirmation_contract
# #endregion Test.AgentChat.TestBuildConfirmationContract
# ═══════════════════════════════════════════════════════════════════
# confirmation_metadata_for_tool
# ═══════════════════════════════════════════════════════════════════
# #region test_confirmation_metadata_for_tool [C:2] [TYPE Class]
# #region Test.AgentChat.TestConfirmationMetadataForTool [C:2] [TYPE Class]
# @BRIEF Test confirmation_metadata_for_tool output shape.
class TestConfirmationMetadataForTool:
def test_includes_all_required_fields(self):
@@ -117,14 +117,14 @@ class TestConfirmationMetadataForTool:
from ss_tools.agent._confirmation import confirmation_metadata_for_tool
meta = confirmation_metadata_for_tool("conv-1", "list_environments")
assert meta["tool_args"] == {}
# #endregion test_confirmation_metadata_for_tool
# #endregion Test.AgentChat.TestConfirmationMetadataForTool
# ═══════════════════════════════════════════════════════════════════
# _format_tool_output_via_llm
# ═══════════════════════════════════════════════════════════════════
# #region test_format_tool_output [C:3] [TYPE Class]
# #region Test.AgentChat.TestFormatToolOutput [C:3] [TYPE Class]
# @BRIEF Integration tests for _format_tool_output_via_llm — LLM path and fallbacks.
class TestFormatToolOutput:
@pytest.mark.asyncio
@@ -235,10 +235,10 @@ class TestFormatToolOutput:
data = _json_chunks(chunks)
assert len(data) == 1
assert data[0]["content"] == raw
# #endregion test_format_tool_output
# #endregion Test.AgentChat.TestFormatToolOutput
# ═══════════════════════════════════════════════════════════════════
# #region test_handle_resume_integration [C:3] [TYPE Class]
# #region Test.AgentChat.TestHandleResumeIntegration [C:3] [TYPE Class]
# @BRIEF Integration tests for handle_resume — fast-path confirm/deny, error paths,
# LLM formatting integration, and title race-condition coverage.
class TestHandleResumeIntegration:
@@ -404,14 +404,14 @@ class TestHandleResumeIntegration:
assert "stream_token" in types
assert "tool_start" in types
assert "tool_end" in types
# #endregion test_handle_resume_integration
# #endregion Test.AgentChat.TestHandleResumeIntegration
# ═══════════════════════════════════════════════════════════════════
# Title race-condition coverage
# ═══════════════════════════════════════════════════════════════════
# #region test_title_race_condition [C:2] [TYPE Class]
# #region Test.AgentChat.TestTitleRaceCondition [C:2] [TYPE Class]
# @BRIEF Verify that agent_handler captures tool_name BEFORE handle_resume pops it,
# so the conversation title is descriptive (e.g. "✅ list_environments"), not
# the fallback "HITL: confirm".
@@ -470,7 +470,7 @@ class TestTitleRaceCondition:
assert tool_name == "", "tool_name should be empty after pop — this IS the bug"
title = f"{tool_name}" if tool_name else "HITL: confirm"
assert title == "HITL: confirm", "Without the fix, title falls back to generic"
# #endregion test_title_race_condition
# #endregion Test.AgentChat.TestTitleRaceCondition
# ── Helper for async iter ─────────────────────────────────────────

View File

@@ -20,44 +20,44 @@ from ss_tools.agent._confirmation import (
# ── _resolve_env_tier ───────────────────────────────────────────────
# #region test_env_resolution_from_tool_args [C:2] [TYPE Function]
# #region Test.Agent.TestEnvResolutionFromToolArgs [C:2] [TYPE Function]
def test_resolve_env_tier_from_tool_args_env_id():
"""env_id in tool_args takes highest priority."""
assert _resolve_env_tier({"env_id": "prod-01"}, None) == "prod"
assert _resolve_env_tier({"env_id": "prod-01"}, "staging") == "prod" # tool_args wins
# #endregion test_env_resolution_from_tool_args
# #endregion Test.Agent.TestEnvResolutionFromToolArgs
# #region test_env_resolution_from_environment_id [C:2] [TYPE Function]
# #region Test.Agent.TestEnvResolutionFromEnvironmentId [C:2] [TYPE Function]
def test_resolve_env_tier_from_environment_id():
"""environment_id in tool_args (alternative key) works."""
assert _resolve_env_tier({"environment_id": "staging-v2"}, None) == "staging"
# #endregion test_env_resolution_from_environment_id
# #endregion Test.Agent.TestEnvResolutionFromEnvironmentId
# #region test_env_resolution_from_target_env [C:2] [TYPE Function]
# #region Test.Agent.TestEnvResolutionFromTargetEnv [C:2] [TYPE Function]
def test_resolve_env_tier_from_target_env():
"""target_env fallback when tool_args has no env."""
assert _resolve_env_tier({}, "ss-dev") == "dev"
assert _resolve_env_tier({"query": "test"}, "prod-v2") == "prod"
# #endregion test_env_resolution_from_target_env
# #endregion Test.Agent.TestEnvResolutionFromTargetEnv
# #region test_env_resolution_null_when_no_env [C:2] [TYPE Function]
# #region Test.Agent.TestEnvResolutionNullWhenNoEnv [C:2] [TYPE Function]
def test_resolve_env_tier_null_when_no_env():
"""When neither tool_args nor target_env provides env, return None."""
assert _resolve_env_tier({}, None) is None
assert _resolve_env_tier({"dashboard_id": 42}, None) is None
# #endregion test_env_resolution_null_when_no_env
# #endregion Test.Agent.TestEnvResolutionNullWhenNoEnv
# #region test_env_resolution_ambiguous_names [C:2] [TYPE Function]
# #region Test.Agent.TestEnvResolutionAmbiguousNames [C:2] [TYPE Function]
def test_resolve_env_tier_ambiguous_names():
"""Environment names containing stag/test/local/dev are correctly tiered."""
assert _resolve_env_tier({"env_id": "autotest"}, None) == "staging" # "test" in autotest
assert _resolve_env_tier({"env_id": "localhost"}, None) == "dev" # "local" in localhost
# #endregion test_env_resolution_ambiguous_names
# #endregion Test.Agent.TestEnvResolutionAmbiguousNames
# ── build_confirmation_contract_v2 ───────────────────────────────────
# #region test_deploy_to_prod_is_guarded_with_prod_context [C:2] [TYPE Function]
# #region Test.Agent.TestDeployToProdIsGuardedWithProdContext [C:2] [TYPE Function]
def test_deploy_to_prod_is_guarded_with_prod_context():
"""Deploy to production → guarded risk + prod env_context."""
contract = build_confirmation_contract_v2(
@@ -68,10 +68,10 @@ def test_deploy_to_prod_is_guarded_with_prod_context():
assert contract["dangerous"] is False
assert contract["env_context"] == "prod"
assert contract["permission_granted"] is True
# #endregion test_deploy_to_prod_is_guarded_with_prod_context
# #endregion Test.Agent.TestDeployToProdIsGuardedWithProdContext
# #region test_deploy_to_staging_is_guarded_with_staging_context [C:2] [TYPE Function]
# #region Test.Agent.TestDeployToStagingIsGuardedWithStagingContext [C:2] [TYPE Function]
def test_deploy_to_staging_is_guarded_with_staging_context():
"""Deploy to staging → guarded risk + staging env_context."""
contract = build_confirmation_contract_v2(
@@ -80,10 +80,10 @@ def test_deploy_to_staging_is_guarded_with_staging_context():
assert contract["risk"] == "write"
assert contract["risk_level"] == "guarded"
assert contract["env_context"] == "staging"
# #endregion test_deploy_to_staging_is_guarded_with_staging_context
# #endregion Test.Agent.TestDeployToStagingIsGuardedWithStagingContext
# #region test_deploy_to_dev_is_guarded_with_dev_context [C:2] [TYPE Function]
# #region Test.Agent.TestDeployToDevIsGuardedWithDevContext [C:2] [TYPE Function]
def test_deploy_to_dev_is_guarded_with_dev_context():
"""Deploy to dev → guarded risk + dev env_context."""
contract = build_confirmation_contract_v2(
@@ -92,10 +92,10 @@ def test_deploy_to_dev_is_guarded_with_dev_context():
assert contract["risk"] == "write"
assert contract["risk_level"] == "guarded"
assert contract["env_context"] == "dev"
# #endregion test_deploy_to_dev_is_guarded_with_dev_context
# #endregion Test.Agent.TestDeployToDevIsGuardedWithDevContext
# #region test_delete_operation_is_dangerous [C:2] [TYPE Function]
# #region Test.Agent.TestDeleteOperationIsDangerous [C:2] [TYPE Function]
def test_delete_operation_is_dangerous():
"""Delete-prefixed tools should be classified as dangerous."""
contract = build_confirmation_contract_v2(
@@ -104,10 +104,10 @@ def test_delete_operation_is_dangerous():
assert contract["risk"] == "write"
assert contract["risk_level"] == "dangerous"
assert contract["dangerous"] is True
# #endregion test_delete_operation_is_dangerous
# #endregion Test.Agent.TestDeleteOperationIsDangerous
# #region test_read_only_tool_is_safe [C:2] [TYPE Function]
# #region Test.Agent.TestReadOnlyToolIsSafe [C:2] [TYPE Function]
def test_read_only_tool_is_safe():
"""Search/list/get tools should be classified as safe/read."""
contract = build_confirmation_contract_v2(
@@ -116,10 +116,10 @@ def test_read_only_tool_is_safe():
assert contract["risk"] == "read"
assert contract["risk_level"] == "safe"
assert contract["dangerous"] is False
# #endregion test_read_only_tool_is_safe
# #endregion Test.Agent.TestReadOnlyToolIsSafe
# #region test_viewer_gets_permission_denied [C:2] [TYPE Function]
# #region Test.Agent.TestViewerGetsPermissionDenied [C:2] [TYPE Function]
def test_viewer_permission_denied_for_write_tools():
"""Viewer role should be denied permission for write tools."""
contract = build_confirmation_contract_v2(
@@ -129,31 +129,31 @@ def test_viewer_permission_denied_for_write_tools():
assert contract["required_role"] == "admin"
assert contract["alternatives"] is not None
assert len(contract["alternatives"]) >= 1
# #endregion test_viewer_gets_permission_denied
# #endregion Test.Agent.TestViewerGetsPermissionDenied
# #region test_analyst_permission_denied_specific_tools [C:2] [TYPE Function]
# #region Test.Agent.TestAnalystPermissionDeniedSpecificTools [C:2] [TYPE Function]
def test_analyst_permission_denied_for_admin_tools():
"""Analyst/editor role should be denied for admin-only tools."""
for tool_name in ("deploy_dashboard", "execute_migration", "run_backup"):
contract = build_confirmation_contract_v2(tool_name, {}, "analyst")
assert contract["permission_granted"] is False, f"{tool_name} should be denied for analyst"
# #endregion test_analyst_permission_denied_specific_tools
# #endregion Test.Agent.TestAnalystPermissionDeniedSpecificTools
# #region test_admin_write_tool_permission_granted [C:2] [TYPE Function]
# #region Test.Agent.TestAdminWriteToolPermissionGranted [C:2] [TYPE Function]
def test_admin_write_tool_permission_granted():
"""Admin role should always have permission_granted=True for guarded tools."""
write_tools = ["deploy_dashboard", "commit_changes", "execute_migration"]
for tool_name in write_tools:
contract = build_confirmation_contract_v2(tool_name, {}, "admin")
assert contract["permission_granted"] is True, f"{tool_name} should be allowed for admin"
# #endregion test_admin_write_tool_permission_granted
# #endregion Test.Agent.TestAdminWriteToolPermissionGranted
# ── confirmation_metadata_for_tool ────────────────────────────────────
# #region test_metadata_for_tool_contains_required_fields [C:2] [TYPE Function]
# #region Test.Agent.TestMetadataForToolContainsRequiredFields [C:2] [TYPE Function]
def test_metadata_for_tool_contains_all_required_fields():
"""confirmation_metadata_for_tool should produce a complete metadata dict."""
meta = confirmation_metadata_for_tool(
@@ -173,12 +173,12 @@ def test_metadata_for_tool_contains_all_required_fields():
assert meta["risk"] == "write"
assert meta["risk_level"] == "guarded"
assert meta["requires_confirmation"] is True
# #endregion test_metadata_for_tool_contains_required_fields
# #endregion Test.Agent.TestMetadataForToolContainsRequiredFields
# ── permission_denied_payload ─────────────────────────────────────────
# #region test_permission_denied_payload_structure [C:2] [TYPE Function]
# #region Test.Agent.TestPermissionDeniedPayloadStructure [C:2] [TYPE Function]
def test_permission_denied_payload_structure():
"""permission_denied_payload should produce valid JSON with correct type."""
import json
@@ -192,10 +192,10 @@ def test_permission_denied_payload_structure():
assert payload["metadata"]["required_role"] == "admin"
assert payload["metadata"]["user_role"] == "viewer"
assert payload["metadata"]["alternatives"] == []
# #endregion test_permission_denied_payload_structure
# #endregion Test.Agent.TestPermissionDeniedPayloadStructure
# #region test_permission_denied_payload_with_alternatives [C:2] [TYPE Function]
# #region Test.Agent.TestPermissionDeniedPayloadWithAlternatives [C:2] [TYPE Function]
def test_permission_denied_payload_with_alternatives():
"""Alternatives list should be preserved in the payload."""
import json
@@ -207,35 +207,35 @@ def test_permission_denied_payload_with_alternatives():
payload = json.loads(payload_str)
assert payload["metadata"]["alternatives"] == alternatives
# #endregion test_permission_denied_payload_with_alternatives
# #endregion Test.Agent.TestPermissionDeniedPayloadWithAlternatives
# ── Unknown/null tool ─────────────────────────────────────────────────
# #region test_null_tool_name_handled [C:2] [TYPE Function]
# #region Test.Agent.TestNullToolNameHandled [C:2] [TYPE Function]
def test_null_tool_name_handled_gracefully():
"""Null tool_name should produce 'unknown_action' fallback."""
contract = build_confirmation_contract_v2(None)
assert contract["operation"] == "unknown_action"
assert contract["risk_level"] == "safe"
# #endregion test_null_tool_name_handled
# #endregion Test.Agent.TestNullToolNameHandled
# #region test_execute_migration_is_guarded [C:2] [TYPE Function]
# #region Test.Agent.TestExecuteMigrationIsGuarded [C:2] [TYPE Function]
def test_execute_migration_is_guarded():
"""execute_migration should be classified as guarded (write)."""
contract = build_confirmation_contract_v2("execute_migration", {})
assert contract["risk"] == "write"
assert contract["risk_level"] == "guarded"
# #endregion test_execute_migration_is_guarded
# #endregion Test.Agent.TestExecuteMigrationIsGuarded
# #region test_commit_changes_is_guarded [C:2] [TYPE Function]
# #region Test.Agent.TestCommitChangesIsGuarded [C:2] [TYPE Function]
def test_commit_changes_is_guarded():
"""commit_changes should be classified as guarded (write)."""
contract = build_confirmation_contract_v2("commit_changes", {})
assert contract["risk"] == "write"
assert contract["risk_level"] == "guarded"
# #endregion test_commit_changes_is_guarded
# #endregion Test.Agent.TestCommitChangesIsGuarded
# #endregion Test.Agent.ConfirmationV2

View File

@@ -19,14 +19,14 @@ import pytest
from ss_tools.agent._context import UIContextValidationError, validate_uicontext
# #region test_null_payload_returns_empty [C:2] [TYPE Function]
# #region Test.Agent.TestNullPayloadReturnsEmpty [C:2] [TYPE Function]
def test_null_payload_returns_empty_dict():
"""Null payloads should safely return an empty dict."""
assert validate_uicontext(None) == {}
# #endregion test_null_payload_returns_empty
# #endregion Test.Agent.TestNullPayloadReturnsEmpty
# #region test_valid_dashboard_context_passes [C:2] [TYPE Function]
# #region Test.Agent.TestValidDashboardContextPasses [C:2] [TYPE Function]
def test_valid_dashboard_context_passes():
"""Full dashboard UIContext with all fields should validate cleanly."""
payload = {
@@ -39,10 +39,10 @@ def test_valid_dashboard_context_passes():
}
result = validate_uicontext(payload)
assert result == payload
# #endregion test_valid_dashboard_context_passes
# #endregion Test.Agent.TestValidDashboardContextPasses
# #region test_valid_dataset_context_passes [C:2] [TYPE Function]
# #region Test.Agent.TestValidDatasetContextPasses [C:2] [TYPE Function]
def test_valid_dataset_context_passes():
"""Dataset UIContext should pass validation."""
payload = {
@@ -54,10 +54,10 @@ def test_valid_dataset_context_passes():
"contextVersion": 1,
}
assert validate_uicontext(payload) == payload
# #endregion test_valid_dataset_context_passes
# #endregion Test.Agent.TestValidDatasetContextPasses
# #region test_valid_migration_context_passes [C:2] [TYPE Function]
# #region Test.Agent.TestValidMigrationContextPasses [C:2] [TYPE Function]
def test_valid_migration_context_passes():
"""Migration UIContext should pass validation."""
payload = {
@@ -69,10 +69,10 @@ def test_valid_migration_context_passes():
"contextVersion": 1,
}
assert validate_uicontext(payload) == payload
# #endregion test_valid_migration_context_passes
# #endregion Test.Agent.TestValidMigrationContextPasses
# #region test_invalid_object_type_raises [C:2] [TYPE Function]
# #region Test.Agent.TestInvalidObjectTypeRaises [C:2] [TYPE Function]
def test_invalid_object_type_raises_validation_error():
"""Unknown objectType values should be rejected."""
payload = {
@@ -84,10 +84,10 @@ def test_invalid_object_type_raises_validation_error():
}
with pytest.raises(UIContextValidationError, match="objectType"):
validate_uicontext(payload)
# #endregion test_invalid_object_type_raises
# #endregion Test.Agent.TestInvalidObjectTypeRaises
# #region test_invalid_object_id_raises [C:2] [TYPE Function]
# #region Test.Agent.TestInvalidObjectIdRaises [C:2] [TYPE Function]
def test_non_numeric_object_id_raises():
"""ObjectId must be a numeric string or None."""
payload = {
@@ -98,10 +98,10 @@ def test_non_numeric_object_id_raises():
}
with pytest.raises(UIContextValidationError, match="objectId"):
validate_uicontext(payload)
# #endregion test_invalid_object_id_raises
# #endregion Test.Agent.TestInvalidObjectIdRaises
# #region test_object_name_exceeds_limit_raises [C:2] [TYPE Function]
# #region Test.Agent.TestObjectNameExceedsLimitRaises [C:2] [TYPE Function]
def test_object_name_exceeds_256_chars_raises():
"""ObjectName longer than 256 characters should be rejected."""
payload = {
@@ -113,10 +113,10 @@ def test_object_name_exceeds_256_chars_raises():
}
with pytest.raises(UIContextValidationError, match="objectName"):
validate_uicontext(payload)
# #endregion test_object_name_exceeds_limit_raises
# #endregion Test.Agent.TestObjectNameExceedsLimitRaises
# #region test_object_name_at_boundary_passes [C:2] [TYPE Function]
# #region Test.Agent.TestObjectNameAtBoundaryPasses [C:2] [TYPE Function]
def test_object_name_at_256_chars_passes():
"""ObjectName at exactly 256 characters should pass."""
payload = {
@@ -127,10 +127,10 @@ def test_object_name_at_256_chars_passes():
"contextVersion": 1,
}
assert validate_uicontext(payload) == payload
# #endregion test_object_name_at_boundary_passes
# #endregion Test.Agent.TestObjectNameAtBoundaryPasses
# #region test_invalid_context_version_raises [C:2] [TYPE Function]
# #region Test.Agent.TestInvalidContextVersionRaises [C:2] [TYPE Function]
def test_invalid_context_version_raises():
"""Only contextVersion=1 is currently supported."""
payload = {
@@ -141,10 +141,10 @@ def test_invalid_context_version_raises():
}
with pytest.raises(UIContextValidationError, match="contextVersion"):
validate_uicontext(payload)
# #endregion test_invalid_context_version_raises
# #endregion Test.Agent.TestInvalidContextVersionRaises
# #region test_missing_context_version_raises [C:2] [TYPE Function]
# #region Test.Agent.TestMissingContextVersionRaises [C:2] [TYPE Function]
def test_missing_context_version_raises():
"""ContextVersion should always be present and equal 1."""
payload = {
@@ -154,10 +154,10 @@ def test_missing_context_version_raises():
}
with pytest.raises(UIContextValidationError):
validate_uicontext(payload)
# #endregion test_missing_context_version_raises
# #endregion Test.Agent.TestMissingContextVersionRaises
# #region test_payload_exceeds_4kb_raises [C:2] [TYPE Function]
# #region Test.Agent.TestPayloadExceeds4KbRaises [C:2] [TYPE Function]
def test_payload_exceeds_4kb_raises():
"""Payloads larger than 4 KB should be rejected to prevent prompt injection."""
payload = {
@@ -170,10 +170,10 @@ def test_payload_exceeds_4kb_raises():
}
with pytest.raises(UIContextValidationError, match="exceeds 4 KB"):
validate_uicontext(payload)
# #endregion test_payload_exceeds_4kb_raises
# #endregion Test.Agent.TestPayloadExceeds4KbRaises
# #region test_route_exceeds_512_chars_raises [C:2] [TYPE Function]
# #region Test.Agent.TestRouteExceeds512CharsRaises [C:2] [TYPE Function]
def test_route_exceeds_512_chars_raises():
"""Route length should be capped at 512 characters."""
payload = {
@@ -184,10 +184,10 @@ def test_route_exceeds_512_chars_raises():
}
with pytest.raises(UIContextValidationError, match="route"):
validate_uicontext(payload)
# #endregion test_route_exceeds_512_chars_raises
# #endregion Test.Agent.TestRouteExceeds512CharsRaises
# #region test_env_id_none_and_string_accepted [C:2] [TYPE Function]
# #region Test.Agent.TestEnvIdNoneAndStringAccepted [C:2] [TYPE Function]
def test_env_id_none_and_string_accepted():
"""envId should accept None and string values."""
assert validate_uicontext({
@@ -199,10 +199,10 @@ def test_env_id_none_and_string_accepted():
"objectType": "dashboard", "objectId": "42",
"envId": "ss-dev", "route": "/dashboards/42", "contextVersion": 1,
})["envId"] == "ss-dev"
# #endregion test_env_id_none_and_string_accepted
# #endregion Test.Agent.TestEnvIdNoneAndStringAccepted
# #region test_without_object_type_passes [C:2] [TYPE Function]
# #region Test.Agent.TestWithoutObjectTypePasses [C:2] [TYPE Function]
def test_no_object_type_with_env_passes():
"""Context without objectType (general mode) should pass validation."""
payload = {
@@ -213,6 +213,6 @@ def test_no_object_type_with_env_passes():
"contextVersion": 1,
}
assert validate_uicontext(payload) == payload
# #endregion test_without_object_type_passes
# #endregion Test.Agent.TestWithoutObjectTypePasses
# #endregion Test.Agent.Context

View File

@@ -155,6 +155,7 @@ async def test_handler_invalid_jwt_continues_gracefully():
# #endregion TestAgentChat.Handler.AuthError
# #endregion TestAgentChat.Handler.AuthGraceful
# #region TestAgentChat.Handler.Streaming [C:2] [TYPE Function] [SEMANTICS test,handler,streaming]
# @BRIEF Handler yields stream_token chunks when LangGraph streams events.
@pytest.mark.anyio

View File

@@ -34,7 +34,7 @@ def _tools(names: list[str]) -> list[SimpleNamespace]:
# ── build_tool_pipeline ──────────────────────────────────────────────
# #region test_null_object_type_returns_all_rbac_allowed [C:2] [TYPE Function]
# #region Test.Agent.TestNullObjectTypeReturnsAllRbacAllowed [C:2] [TYPE Function]
def test_null_object_type_returns_all_rbac_allowed_tools():
"""With no object_type, all tools pass except those blocked by RBAC (viewer)."""
tools = _tools(["search_dashboards", "deploy_dashboard", "show_capabilities"])
@@ -50,10 +50,10 @@ def test_null_object_type_returns_all_rbac_allowed_tools():
assert "deploy_dashboard" not in result_viewer
assert "search_dashboards" in result_viewer
assert "show_capabilities" in result_viewer
# #endregion test_null_object_type_returns_all_rbac_allowed
# #endregion Test.Agent.TestNullObjectTypeReturnsAllRbacAllowed
# #region test_dashboard_context_admin [C:2] [TYPE Function]
# #region Test.Agent.TestDashboardContextAdmin [C:2] [TYPE Function]
def test_dashboard_context_admin_keeps_affinity_tools():
"""Dashboard context + admin role: keep all dashboard tools + capabilities."""
tools = _tools([
@@ -70,10 +70,10 @@ def test_dashboard_context_admin_keeps_affinity_tools():
assert "show_capabilities" in result
assert "run_backup" not in result
assert "superset_execute_sql" not in result
# #endregion test_dashboard_context_admin
# #endregion Test.Agent.TestDashboardContextAdmin
# #region test_dashboard_context_viewer [C:2] [TYPE Function]
# #region Test.Agent.TestDashboardContextViewer [C:2] [TYPE Function]
def test_dashboard_context_viewer_removes_admin_only():
"""Dashboard context + viewer: admin-only tools removed even from dashboard affinity."""
tools = _tools([
@@ -87,10 +87,10 @@ def test_dashboard_context_viewer_removes_admin_only():
assert "deploy_dashboard" not in result
assert "execute_migration" not in result
assert "commit_changes" not in result
# #endregion test_dashboard_context_viewer
# #endregion Test.Agent.TestDashboardContextViewer
# #region test_dataset_context_admin [C:2] [TYPE Function]
# #region Test.Agent.TestDatasetContextAdmin [C:2] [TYPE Function]
def test_dataset_context_admin_keeps_dataset_tools():
"""Dataset context + admin: keep dataset affinity tools, exclude non-dataset."""
tools = _tools([
@@ -108,10 +108,10 @@ def test_dataset_context_admin_keeps_dataset_tools():
assert "show_capabilities" in result
assert "deploy_dashboard" not in result
assert "run_backup" not in result
# #endregion test_dataset_context_admin
# #endregion Test.Agent.TestDatasetContextAdmin
# #region test_dataset_context_viewer [C:2] [TYPE Function]
# #region Test.Agent.TestDatasetContextViewer [C:2] [TYPE Function]
def test_dataset_context_viewer_removes_admin_tools():
"""Dataset context + viewer: admin-only tools in dataset affinity removed."""
tools = _tools([
@@ -124,10 +124,10 @@ def test_dataset_context_viewer_removes_admin_tools():
assert "superset_execute_sql" in result
assert "show_capabilities" in result
assert "start_maintenance" not in result
# #endregion test_dataset_context_viewer
# #endregion Test.Agent.TestDatasetContextViewer
# #region test_migration_context_admin [C:2] [TYPE Function]
# #region Test.Agent.TestMigrationContextAdmin [C:2] [TYPE Function]
def test_migration_context_admin_keeps_migration_tools():
"""Migration context + admin: keep migration affinity tools, exclude others."""
tools = _tools([
@@ -142,10 +142,10 @@ def test_migration_context_admin_keeps_migration_tools():
assert "show_capabilities" in result
assert "run_backup" not in result
assert "superset_execute_sql" not in result
# #endregion test_migration_context_admin
# #endregion Test.Agent.TestMigrationContextAdmin
# #region test_unknown_object_type_falls_back [C:2] [TYPE Function]
# #region Test.Agent.TestUnknownObjectTypeFallsBack [C:2] [TYPE Function]
def test_unknown_object_type_falls_back_to_full_list():
"""Unknown object_type should not filter — behaves like null context."""
tools = _tools([
@@ -159,10 +159,10 @@ def test_unknown_object_type_falls_back_to_full_list():
assert "run_backup" in result
assert "superset_execute_sql" in result
assert "show_capabilities" in result
# #endregion test_unknown_object_type_falls_back
# #endregion Test.Agent.TestUnknownObjectTypeFallsBack
# #region test_show_capabilities_always_included [C:2] [TYPE Function]
# #region Test.Agent.TestShowCapabilitiesAlwaysIncluded [C:2] [TYPE Function]
def test_show_capabilities_always_included():
"""show_capabilities must survive all filtering stages regardless of context."""
tools = _tools(["show_capabilities"])
@@ -173,10 +173,10 @@ def test_show_capabilities_always_included():
assert "show_capabilities" in result, (
f"show_capabilities missing for role={role}, object_type={obj_type}"
)
# #endregion test_show_capabilities_always_included
# #endregion Test.Agent.TestShowCapabilitiesAlwaysIncluded
# #region test_pipeline_is_idempotent [C:2] [TYPE Function]
# #region Test.Agent.TestPipelineIsIdempotent [C:2] [TYPE Function]
def test_pipeline_does_not_mutate_input_list():
"""build_tool_pipeline must return a new list and not mutate the input."""
tools = _tools(["search_dashboards", "show_capabilities"])
@@ -185,12 +185,12 @@ def test_pipeline_does_not_mutate_input_list():
# Input list unchanged
assert [id(t) for t in tools] == original_ids
assert len(tools) == 2
# #endregion test_pipeline_is_idempotent
# #endregion Test.Agent.TestPipelineIsIdempotent
# ── enforce_tool_permission ──────────────────────────────────────────
# #region test_invocation_guard_admin_allowed [C:2] [TYPE Function]
# #region Test.Agent.TestInvocationGuardAdminAllowed [C:2] [TYPE Function]
def test_enforce_tool_permission_admin_allowed():
"""Admin role should be allowed to invoke all restricted tools."""
restricted = ["deploy_dashboard", "commit_changes", "create_branch",
@@ -199,10 +199,10 @@ def test_enforce_tool_permission_admin_allowed():
assert enforce_tool_permission(tool_name, "admin") is True, (
f"Admin should be allowed to invoke '{tool_name}'"
)
# #endregion test_invocation_guard_admin_allowed
# #endregion Test.Agent.TestInvocationGuardAdminAllowed
# #region test_invocation_guard_viewer_denied [C:2] [TYPE Function]
# #region Test.Agent.TestInvocationGuardViewerDenied [C:2] [TYPE Function]
def test_enforce_tool_permission_viewer_denied():
"""Viewer role should be denied for all restricted tools."""
restricted = ["deploy_dashboard", "commit_changes", "create_branch",
@@ -211,19 +211,19 @@ def test_enforce_tool_permission_viewer_denied():
assert enforce_tool_permission(tool_name, "viewer") is False, (
f"Viewer should NOT be allowed to invoke '{tool_name}'"
)
# #endregion test_invocation_guard_viewer_denied
# #endregion Test.Agent.TestInvocationGuardViewerDenied
# #region test_invocation_guard_unknown_tool_allowed [C:2] [TYPE Function]
# #region Test.Agent.TestInvocationGuardUnknownToolAllowed [C:2] [TYPE Function]
def test_enforce_tool_permission_unknown_tool_always_allowed():
"""Unknown tools (not in _TOOL_PERMISSIONS) should always be allowed."""
assert enforce_tool_permission("search_dashboards", "viewer") is True
assert enforce_tool_permission("show_capabilities", "viewer") is True
assert enforce_tool_permission("nonexistent_tool", "viewer") is True
# #endregion test_invocation_guard_unknown_tool_allowed
# #endregion Test.Agent.TestInvocationGuardUnknownToolAllowed
# #region test_context_affinity_coverage [C:2] [TYPE Function]
# #region Test.Agent.TestContextAffinityCoverage [C:2] [TYPE Function]
def test_context_affinity_maps_exist_for_all_expected_types():
"""Verify that all three known object types have affinity mappings."""
assert "dashboard" in _CONTEXT_TOOL_AFFINITY
@@ -234,10 +234,10 @@ def test_context_affinity_maps_exist_for_all_expected_types():
assert len(_CONTEXT_TOOL_AFFINITY[obj_type]) >= 3, (
f"Context affinity for '{obj_type}' should have ≥3 tools"
)
# #endregion test_context_affinity_coverage
# #endregion Test.Agent.TestContextAffinityCoverage
# #region test_rbac_maps_exist_for_write_tools [C:2] [TYPE Function]
# #region Test.Agent.TestRbacMapsExistForWriteTools [C:2] [TYPE Function]
def test_rbac_permissions_for_write_tools():
"""Verify all admin-only tools are explicitly listed in _TOOL_PERMISSIONS."""
expected_admin_tools = [
@@ -253,3 +253,4 @@ def test_rbac_permissions_for_write_tools():
# #endregion Test.Agent.ToolFilter
# #endregion Test.Agent.TestRbacMapsExistForWriteTools

View File

@@ -72,7 +72,7 @@ def mock_request():
return req
# #region test_extract_user_id [C:2] [TYPE Function]
# #region Test.AgentChat.TestExtractUserId [C:2] [TYPE Function]
# @BRIEF Test extract_user_id for various JWT payloads.
class TestExtractUserId:
def test_extracts_sub(self):
@@ -99,10 +99,10 @@ class TestExtractUserId:
assert extract_user_id("") == "unknown"
# #endregion test_extract_user_id
# #endregion Test.AgentChat.TestExtractUserId
# #region test_confirmation_metadata [C:2] [TYPE Function]
# #region Test.AgentChat.TestConfirmationMetadata [C:2] [TYPE Function]
# @BRIEF Test backend HITL confirmation contract exposed to the frontend.
class TestConfirmationMetadata:
def test_extracts_tool_call_and_read_contract(self):
@@ -221,10 +221,10 @@ class TestConfirmationMetadata:
assert metadata_types[:3] == ["confirm_resolved", "tool_start", "tool_end"]
# #endregion test_confirmation_metadata
# #endregion Test.AgentChat.TestConfirmationMetadata
# #region test_agent_handler [C:2] [TYPE Function]
# #region Test.AgentChat.TestAgentHandler [C:2] [TYPE Function]
# @BRIEF Test agent_handler for various scenarios.
class TestAgentHandler:
@pytest.mark.asyncio
@@ -537,10 +537,10 @@ class TestAgentHandler:
assert get_user_jwt() == "", "JWT should be cleared after handler completes"
# #endregion test_agent_handler
# #endregion Test.AgentChat.TestAgentHandler
# #region test_handle_resume [C:2] [TYPE Function]
# #region Test.AgentChat.TestHandleResume [C:2] [TYPE Function]
# @BRIEF Test handle_resume LangGraph checkpoint resume (no pending confirmation).
class TestHandleResume:
@pytest.mark.asyncio
@@ -566,10 +566,10 @@ class TestHandleResume:
assert data["metadata"]["result"] == "denied"
# #endregion test_handle_resume
# #endregion Test.AgentChat.TestHandleResume
# #region test_save_conversation [C:2] [TYPE Function]
# #region Test.AgentChat.TestSaveConversation [C:2] [TYPE Function]
# @BRIEF Test save_conversation with various scenarios.
class TestSaveConversation:
@pytest.mark.asyncio
@@ -615,10 +615,10 @@ class TestSaveConversation:
assert call_kwargs["json"]["title"] == "Новый диалог"
# #endregion test_save_conversation
# #endregion Test.AgentChat.TestSaveConversation
# #region test_lifecycle_events [C:2] [TYPE Function]
# #region Test.AgentChat.TestLifecycleEvents [C:2] [TYPE Function]
# @BRIEF Test lifecycle event emission in agent_handler: AGENT_REQUEST_STARTED, AGENT_LLM_*, AGENT_REQUEST_COMPLETED/FAILED.
class TestLifecycleEvents:
@pytest.mark.asyncio
@@ -800,10 +800,10 @@ class TestLifecycleEvents:
assert payload["tool_count"] >= 1
# #endregion test_lifecycle_events
# #endregion Test.AgentChat.TestLifecycleEvents
# #region test_create_chat_interface [C:2] [TYPE Function]
# #region Test.AgentChat.TestCreateChatInterface [C:2] [TYPE Function]
# @BRIEF Test create_chat_interface returns a gr.ChatInterface.
class TestCreateChatInterface:
def test_returns_chat_interface(self):
@@ -814,10 +814,10 @@ class TestCreateChatInterface:
assert result is mock_ci.return_value
# #endregion test_create_chat_interface
# #endregion Test.AgentChat.TestCreateChatInterface
# #region test_health [C:2] [TYPE Function]
# #region Test.AgentChat.TestHealth [C:2] [TYPE Function]
# @BRIEF Test health endpoint returns status ok.
class TestHealth:
@pytest.mark.asyncio
@@ -828,10 +828,10 @@ class TestHealth:
assert result["status"] == "ok"
# #endregion test_health
# #endregion Test.AgentChat.TestHealth
# #region test_file_upload_parsing [C:2] [TYPE Function]
# #region Test.AgentChat.TestFileUploadParsing [C:2] [TYPE Function]
# @BRIEF Test file upload branch — parse_upload called for valid small files.
class TestFileUploadParsing:
@pytest.mark.asyncio
@@ -863,10 +863,10 @@ class TestFileUploadParsing:
assert token_data["metadata"]["type"] == "stream_token"
# #endregion test_file_upload_parsing
# #endregion Test.AgentChat.TestFileUploadParsing
# #region test_app_main_block [C:2] [TYPE Function]
# #region Test.AgentChat.TestAppMainBlock [C:2] [TYPE Function]
# @BRIEF Test if __name__ == '__main__' block in app.py.
class TestAppMainBlock:
def test_app_main_block(self):
@@ -890,5 +890,5 @@ class TestAppMainBlock:
mock_demo.launch.assert_called_once()
# #endregion test_app_main_block
# #endregion Test.AgentChat.TestAppMainBlock
# #endregion Test.AgentChat.GradioApp

View File

@@ -18,7 +18,7 @@ def _tools(names: list[str]) -> list[SimpleNamespace]:
return [SimpleNamespace(name=name) for name in names]
# #region test_dashboard_context_filters_tools [C:2] [TYPE Function]
# #region Test.Agent.TestDashboardContextFiltersTools [C:2] [TYPE Function]
# @BRIEF Dashboard context keeps only dashboard-affinity tools and mandatory capabilities.
def test_dashboard_context_filters_tools():
tools = _tools([
@@ -38,10 +38,10 @@ def test_dashboard_context_filters_tools():
"deploy_dashboard",
"show_capabilities",
]
# #endregion test_dashboard_context_filters_tools
# #endregion Test.Agent.TestDashboardContextFiltersTools
# #region test_dashboard_context_viewer_removes_admin_tools [C:2] [TYPE Function]
# #region Test.Agent.TestDashboardContextViewerRemovesAdminTools [C:2] [TYPE Function]
# @BRIEF Viewer role removes admin-only tools even when they are dashboard-affinity tools.
def test_dashboard_context_viewer_removes_admin_tools():
tools = _tools([
@@ -54,20 +54,20 @@ def test_dashboard_context_viewer_removes_admin_tools():
result = [tool.name for tool in build_tool_pipeline(tools, "viewer", "dashboard")]
assert result == ["search_dashboards", "show_capabilities"]
# #endregion test_dashboard_context_viewer_removes_admin_tools
# #endregion Test.Agent.TestDashboardContextViewerRemovesAdminTools
# #region test_invocation_guard_blocks_mutating_tool [C:2] [TYPE Function]
# #region Test.Agent.TestInvocationGuardBlocksMutatingTool [C:2] [TYPE Function]
# @BRIEF Invocation guard rejects admin-only tools for non-admin role before side effects.
def test_invocation_guard_blocks_mutating_tool():
set_user_role("viewer")
with pytest.raises(PermissionError, match="PERMISSION_DENIED:deploy_dashboard:admin:viewer"):
_guard_tool_permission("deploy_dashboard")
# #endregion test_invocation_guard_blocks_mutating_tool
# #endregion Test.Agent.TestInvocationGuardBlocksMutatingTool
# #region test_summarise_response_preserves_json_array_shape [C:2] [TYPE Function]
# #region Test.Agent.TestSummariseResponsePreservesJsonArrayShape [C:2] [TYPE Function]
# @BRIEF Large JSON arrays are summarised as top-N plus total count, not cut mid-structure.
def test_summarise_response_preserves_json_array_shape():
text = "[" + ",".join(f'{{"id":{idx},"name":"dashboard-{idx}"}}' for idx in range(20)) + "]"
@@ -77,6 +77,6 @@ def test_summarise_response_preserves_json_array_shape():
assert summary.startswith("Found 20 items:")
assert "dashboard-0" in summary
assert "15 more items" in summary
# #endregion test_summarise_response_preserves_json_array_shape
# #endregion Test.Agent.TestSummariseResponsePreservesJsonArrayShape
# #endregion Test.Agent.Feature035

View File

@@ -16,7 +16,7 @@ def anyio_backend():
return "asyncio"
# #region test_configure_from_api [C:2] [TYPE Function]
# #region Test.AgentChat.TestConfigureFromApi [C:2] [TYPE Function]
# @BRIEF Test configure_from_api updates global config.
class TestConfigureFromApi:
def test_sets_llm_config(self):
@@ -36,10 +36,10 @@ class TestConfigureFromApi:
ls.configure_from_api({"configured": False})
assert ls._llm_config["configured"] is False
ls._llm_config = None
# #endregion test_configure_from_api
# #endregion Test.AgentChat.TestConfigureFromApi
# #region test_llm_diagnostics [C:2] [TYPE Function] [SEMANTICS test,agent,llm,observability]
# #region Test.AgentChat.TestLlmDiagnostics [C:2] [TYPE Function] [SEMANTICS test,agent,llm,observability]
# @BRIEF Diagnostics identify the configured provider but never expose API credentials or full URL paths.
def test_llm_diagnostics_redacts_api_key_and_path():
import ss_tools.agent.langgraph_setup as ls
@@ -61,10 +61,10 @@ def test_llm_diagnostics_redacts_api_key_and_path():
assert "api_key" not in diagnostics
assert "base_url" not in diagnostics
assert all("secret" not in str(value) for value in diagnostics.values())
# #endregion test_llm_diagnostics
# #endregion Test.AgentChat.TestLlmDiagnostics
# #region test_create_agent [C:2] [TYPE Function]
# #region Test.AgentChat.TestCreateAgent [C:2] [TYPE Function]
# @BRIEF Test create_agent with various LLM config states.
class TestCreateAgent:
@pytest.mark.anyio
@@ -206,5 +206,5 @@ class TestCreateAgent:
await ls.create_agent([], interrupt_before=[])
assert mock_create.call_args[1]["interrupt_before"] == []
ls._llm_config = None
# #endregion test_create_agent
# #endregion Test.AgentChat.TestCreateAgent
# #endregion Test.AgentChat.LangGraph.Setup

View File

@@ -12,7 +12,7 @@ from unittest.mock import MagicMock, patch
import pytest
# #region test_emit_lifecycle_event [C:2] [TYPE Function]
# #region Test.AgentChat.TestEmitLifecycleEvent [C:2] [TYPE Function]
# @BRIEF Test emit_lifecycle_event for correct event type and payload.
class TestEmitLifecycleEvent:
def test_emits_event_with_correct_type_and_payload(self):
@@ -84,10 +84,10 @@ class TestEmitLifecycleEvent:
assert payload == {"conversation_id": "conv-1"}
# #endregion test_emit_lifecycle_event
# #endregion Test.AgentChat.TestEmitLifecycleEvent
# #region test_extract_trace_id_from_request [C:2] [TYPE Function]
# #region Test.AgentChat.TestExtractTraceIdFromRequest [C:2] [TYPE Function]
# @BRIEF Test extract_trace_id_from_request with valid/invalid/missing X-Trace-ID headers.
class TestExtractTraceIdFromRequest:
def make_request(self, headers: dict | None = None) -> MagicMock:
@@ -164,10 +164,10 @@ class TestExtractTraceIdFromRequest:
mock_seed.assert_called_once()
# #endregion test_extract_trace_id_from_request
# #endregion Test.AgentChat.TestExtractTraceIdFromRequest
# #region test_log_tool_event [C:2] [TYPE Function]
# #region Test.AgentChat.TestLogToolEvent [C:2] [TYPE Function]
# @BRIEF Test log_tool_event for various event types.
class TestLogToolEvent:
@pytest.mark.asyncio
@@ -278,5 +278,5 @@ class TestLogToolEvent:
# No exception = success
# #endregion test_log_tool_event
# #endregion Test.AgentChat.TestLogToolEvent
# #endregion Test.AgentChat.Middleware

View File

@@ -15,7 +15,7 @@ import subprocess
import sys
# #region test_installed_packages_expose_agent_entrypoint [C:2] [TYPE Function] [SEMANTICS test,agent,packaging,entrypoint]
# #region Test.AgentChat.TestInstalledPackagesExposeAgentEntrypoint [C:2] [TYPE Function] [SEMANTICS test,agent,packaging,entrypoint]
# @BRIEF Install shared and agent distributions into an isolated target and import the entry point.
def test_installed_packages_expose_agent_entrypoint(tmp_path: Path) -> None:
"""run.sh's module entry point is available without source-tree path injection."""
@@ -51,7 +51,7 @@ def test_installed_packages_expose_agent_entrypoint(tmp_path: Path) -> None:
check=False,
)
assert imported.returncode == 0, imported.stderr
# #endregion test_installed_packages_expose_agent_entrypoint
# #endregion Test.AgentChat.TestInstalledPackagesExposeAgentEntrypoint
# #endregion Test.AgentChat.Packaging

View File

@@ -8,7 +8,7 @@ from unittest.mock import MagicMock, patch
import pytest
# #region test_find_free_port [C:2] [TYPE Function]
# #region Test.AgentChat.TestFindFreePort [C:2] [TYPE Function]
# @BRIEF Test _find_free_port for port scanning behavior.
class TestFindFreePort:
def test_returns_free_port(self):
@@ -45,10 +45,10 @@ class TestFindFreePort:
with pytest.raises(OSError, match="No free port found"):
_find_free_port(8000, 3)
assert mock_instance.bind.call_count == 3
# #endregion test_find_free_port
# #endregion Test.AgentChat.TestFindFreePort
# #region test_fetch_llm_config [C:2] [TYPE Function]
# #region Test.AgentChat.TestFetchLlmConfig [C:2] [TYPE Function]
# @BRIEF Test _fetch_llm_config with retry and fallback behavior.
class TestFetchLlmConfig:
def test_returns_config_on_success(self):
@@ -120,10 +120,10 @@ class TestFetchLlmConfig:
assert call_kwargs["headers"].get("Authorization") == "Bearer test-token"
# #endregion test_fetch_llm_config
# #endregion Test.AgentChat.TestFetchLlmConfig
# #region test_main_block [C:2] [TYPE Function]
# #region Test.AgentChat.TestMainBlock [C:2] [TYPE Function]
# @BRIEF Test if __name__ == '__main__' block — service JWT, LLM config, port fallback, OSError.
class TestMainBlock:
"""Test the if __name__ == '__main__' entry point block via importlib.util fresh module."""
@@ -246,5 +246,5 @@ class TestMainBlock:
"GRADIO_ALLOW_PORT_FALLBACK": "true",
},
port_always_fail=True)
# #endregion test_main_block
# #endregion Test.AgentChat.TestMainBlock
# #endregion Test.AgentChat.Run

View File

@@ -44,7 +44,7 @@ def _make_read_timeout() -> httpx.ReadTimeout:
class TestRetryReadTool:
"""Contract tests for _retry_read_tool — the fixed-delay retry wrapper."""
# #region test_first_attempt_502_retries_once [C:2] [TYPE Function]
# #region Test.AgentChat.TestFirstAttempt502RetriesOnce [C:2] [TYPE Function]
# @BRIEF First attempt raises 502 → retries once → second attempt succeeds.
async def test_first_attempt_502_retries_once(self):
"""Prove @TEST_EDGE first_attempt_502: one retry + 1s delay → success."""
@@ -68,9 +68,9 @@ class TestRetryReadTool:
"max_attempts": 2,
},
}]
# #endregion test_first_attempt_502_retries_once
# #endregion Test.AgentChat.TestFirstAttempt502RetriesOnce
# #region test_both_attempts_502_raises [C:2] [TYPE Function]
# #region Test.AgentChat.TestBothAttempts502Raises [C:2] [TYPE Function]
# @BRIEF Both attempts raise 502 → exhaust retries → raises original error.
async def test_both_attempts_502_raises(self):
"""Prove @TEST_EDGE both_attempts_502: max 2 attempts, then raise."""
@@ -82,9 +82,9 @@ class TestRetryReadTool:
assert exc_info.value is error_502
assert mock_fn.call_count == 2
# #endregion test_both_attempts_502_raises
# #endregion Test.AgentChat.TestBothAttempts502Raises
# #region test_connect_error_retried [C:2] [TYPE Function]
# #region Test.AgentChat.TestConnectErrorRetried [C:2] [TYPE Function]
# @BRIEF ConnectError is also retried — not just HTTP status errors.
async def test_connect_error_retried(self):
"""Prove ConnectError triggers the retry path."""
@@ -97,9 +97,9 @@ class TestRetryReadTool:
assert result == expected
assert mock_fn.call_count == 2
# #endregion test_connect_error_retried
# #endregion Test.AgentChat.TestConnectErrorRetried
# #region test_read_timeout_retried [C:2] [TYPE Function]
# #region Test.AgentChat.TestReadTimeoutRetried [C:2] [TYPE Function]
# @BRIEF ReadTimeout is also retried — transient I/O timeouts are recoverable.
async def test_read_timeout_retried(self):
"""Prove ReadTimeout triggers the retry path."""
@@ -112,9 +112,9 @@ class TestRetryReadTool:
assert result == expected
assert mock_fn.call_count == 2
# #endregion test_read_timeout_retried
# #endregion Test.AgentChat.TestReadTimeoutRetried
# #region test_retry_skips_delay_on_success [C:2] [TYPE Function]
# #region Test.AgentChat.TestRetrySkipsDelayOnSuccess [C:2] [TYPE Function]
# @BRIEF When first attempt succeeds, no sleep occurs at all.
async def test_retry_skips_delay_on_success(self):
"""Prove that the happy path never sleeps — sleep is only for retries."""
@@ -127,9 +127,9 @@ class TestRetryReadTool:
assert result == expected
assert mock_fn.call_count == 1
mock_sleep.assert_not_awaited()
# #endregion test_retry_skips_delay_on_success
# #endregion Test.AgentChat.TestRetrySkipsDelayOnSuccess
# #region test_non_http_error_not_retried [C:2] [TYPE Function]
# #region Test.AgentChat.TestNonHttpErrorNotRetried [C:2] [TYPE Function]
# @BRIEF Non-HTTP errors (e.g. ValueError) propagate immediately — no retry.
async def test_non_http_error_not_retried(self):
"""Prove that only the three specific httpx exception types are retried."""
@@ -142,13 +142,13 @@ class TestRetryReadTool:
assert exc_info.value is non_http_err
assert mock_fn.call_count == 1
mock_sleep.assert_not_awaited()
# #endregion test_non_http_error_not_retried
# #endregion Test.AgentChat.TestNonHttpErrorNotRetried
class TestWriteToolNoRetry:
"""Prove that write tools bypass _retry_read_tool entirely."""
# #region test_write_tool_502_no_retry [C:2] [TYPE Function]
# #region Test.AgentChat.TestWriteTool502NoRetry [C:2] [TYPE Function]
# @BRIEF Write tool (is_write=True) gets 502 → no retry, raises immediately.
async def test_write_tool_502_raises_immediately(self):
"""Prove @TEST_EDGE write_tool_502: _execute_with_timeout does NOT retry writes.
@@ -172,7 +172,7 @@ class TestWriteToolNoRetry:
assert write_op.call_count == 1
# Critical invariant: no sleep = no retry loop entered
mock_sleep.assert_not_awaited()
# #endregion test_write_tool_502_no_retry
# #endregion Test.AgentChat.TestWriteTool502NoRetry
# #endregion Test.AgentChat.ToolRetry

View File

@@ -11,7 +11,7 @@ import json
from ss_tools.agent.tools import _summarise_response
# #region test_summarise_json_array_50_items [C:2] [TYPE Function]
# #region Test.AgentChat.TestSummariseJsonArray50Items [C:2] [TYPE Function]
# @BRIEF JSON array with 50 items → top-5 summary with remaining count.
def test_summarise_json_array_50_items():
"""Large JSON arrays summarise with top-5 items and remaining count."""
@@ -24,10 +24,10 @@ def test_summarise_json_array_50_items():
assert "item-0" in summary
assert "item-4" in summary
assert "... and 45 more items." in summary
# #endregion test_summarise_json_array_50_items
# #endregion Test.AgentChat.TestSummariseJsonArray50Items
# #region test_summarise_short_text_passthrough [C:2] [TYPE Function]
# #region Test.AgentChat.TestSummariseShortTextPassthrough [C:2] [TYPE Function]
# @BRIEF Text ≤ limit is returned unchanged (no truncation, no JSON parse overhead visible).
def test_summarise_short_text_passthrough():
"""Text within limit is returned unchanged."""
@@ -36,10 +36,10 @@ def test_summarise_short_text_passthrough():
result = _summarise_response(text, limit=100)
assert result == text
# #endregion test_summarise_short_text_passthrough
# #endregion Test.AgentChat.TestSummariseShortTextPassthrough
# #region test_summarise_json_object_keys_sample [C:2] [TYPE Function]
# #region Test.AgentChat.TestSummariseJsonObjectKeysSample [C:2] [TYPE Function]
# @BRIEF Large JSON object → key list + sample values.
def test_summarise_json_object_keys_sample():
"""Large JSON objects are summarised with keys and sample values."""
@@ -50,10 +50,10 @@ def test_summarise_json_object_keys_sample():
assert summary.startswith("Result keys: ")
assert "Sample: " in summary
# #endregion test_summarise_json_object_keys_sample
# #endregion Test.AgentChat.TestSummariseJsonObjectKeysSample
# #region test_summarise_non_json_sentence_boundary [C:2] [TYPE Function]
# #region Test.AgentChat.TestSummariseNonJsonSentenceBoundary [C:2] [TYPE Function]
# @BRIEF Non-JSON long text truncated at last sentence boundary before limit.
def test_summarise_non_json_sentence_boundary():
"""Non-JSON text truncates at last sentence boundary with trailing ellipsis."""
@@ -67,7 +67,7 @@ def test_summarise_non_json_sentence_boundary():
assert len(summary) <= 500
# Must retain at least one sentence boundary before the ellipsis
assert ". " in summary[:-3]
# #endregion test_summarise_non_json_sentence_boundary
# #endregion Test.AgentChat.TestSummariseNonJsonSentenceBoundary
# #endregion Test.AgentChat.ToolSummarise

View File

@@ -14,7 +14,7 @@ import pytest
# ── Tests ───────────────────────────────────────────────────────────
# #region test_completes_under_timeout [C:2] [TYPE Function]
# #region Test.AgentChat.TestCompletesUnderTimeout [C:2] [TYPE Function]
# @BRIEF GIVEN a tool that returns quickly WHEN _execute_with_timeout is called with a 30s timeout THEN the result is returned normally.
@pytest.mark.asyncio
async def test_completes_under_timeout():
@@ -30,10 +30,10 @@ async def test_completes_under_timeout():
assert result == expected
fast_fn.assert_called_once()
mock_explore.assert_not_called()
# #endregion test_completes_under_timeout
# #endregion Test.AgentChat.TestCompletesUnderTimeout
# #region test_read_tool_timeout [C:2] [TYPE Function]
# #region Test.AgentChat.TestReadToolTimeout [C:2] [TYPE Function]
# @BRIEF GIVEN a read tool that exceeds the timeout WHEN _execute_with_timeout is called THEN TimeoutError is raised and logger.explore is invoked.
@pytest.mark.asyncio
async def test_read_tool_timeout():
@@ -56,10 +56,10 @@ async def test_read_tool_timeout():
assert payload["timeout_s"] == 0.05
assert payload["is_write"] is False
assert call_args[1]["extra"]["src"] == "AgentChat.Tools.Timeout"
# #endregion test_read_tool_timeout
# #endregion Test.AgentChat.TestReadToolTimeout
# #region test_write_tool_timeout [C:2] [TYPE Function]
# #region Test.AgentChat.TestWriteToolTimeout [C:2] [TYPE Function]
# @BRIEF GIVEN a write tool that exceeds the timeout WHEN _execute_with_timeout is called THEN TimeoutError is raised with is_write=True logged.
@pytest.mark.asyncio
async def test_write_tool_timeout():
@@ -82,6 +82,6 @@ async def test_write_tool_timeout():
assert payload["timeout_s"] == 0.05
assert payload["is_write"] is True
assert call_args[1]["extra"]["src"] == "AgentChat.Tools.Timeout"
# #endregion test_write_tool_timeout
# #endregion Test.AgentChat.TestWriteToolTimeout
# #endregion Test.AgentChat.ToolTimeout

View File

@@ -1,11 +1,11 @@
# #region AlembicEnvModule [C:3] [TYPE Module] [SEMANTICS alembic, migration, env, logging]
# #region Alembic.Env.AlembicEnvModule [C:3] [TYPE Module] [SEMANTICS alembic, migration, env, logging]
# @BRIEF Alembic environment configuration — sets up DB connection, model metadata,
# and logging. Contains ADR [LOG-001] for suppress_existing_loggers=False.
# @LAYER Infrastructure
# @RELATION DEPENDS_ON -> [LoggerModule]
# @RELATION DEPENDS_ON -> [Core.Logger.LoggerModule]
# @INVARIANT fileConfig() must be called with disable_existing_loggers=False
# to prevent disabling superset_tools_app logger (see ADR LOG-001).
# #endregion AlembicEnvModule
# #endregion Alembic.Env.AlembicEnvModule
from logging.config import fileConfig
import os

View File

@@ -1,6 +1,6 @@
# #region Alembic.AddDeploymentRecords [C:3] [TYPE Module] [SEMANTICS alembic,migration,deployment,versioning]
# @BRIEF Add deployment_records table for version tracking (Phase 0).
# @RELATION DEPENDS_ON -> [DeploymentModels]
# @RELATION DEPENDS_ON -> [Models.Deployment.DeploymentModels]
# @POST Creates deployment dependency tables before adding foreign-key-constrained records.
# @RATIONALE DeploymentEnvironment and GitRepository were historically created by runtime metadata,
# which left a fresh Alembic upgrade without the foreign-key targets required here.

View File

@@ -1,4 +1,4 @@
# #region SrcRoot [TYPE Module] [SEMANTICS root, package]
# #region Init.SrcRoot [TYPE Module] [SEMANTICS root, package]
# @defgroup Module Module group.
# @BRIEF Canonical backend package root for application, scripts, and tests.
# #endregion SrcRoot
# #endregion Init.SrcRoot

View File

@@ -7,8 +7,8 @@
# @DATA_CONTRACT OAuth2PasswordRequestForm -> Token | User
# @INVARIANT All auth endpoints return consistent error codes (401/403/422).
# @RELATION DEPENDS_ON -> [Auth.Jwt]
# @RELATION DEPENDS_ON -> [auth_service]
# @RELATION DEPENDS_ON -> [AuthOauthModule]
# @RELATION DEPENDS_ON -> [Services.Auth.Service]
# @RELATION DEPENDS_ON -> [Core.Oauth.AuthOauthModule]
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
@@ -39,7 +39,7 @@ router = APIRouter(prefix="/api/auth", tags=["auth"])
# @POST Returns Token(access_token, token_type) on success; 401 on failure.
# @SIDE_EFFECT DB read for user verification; writes security event LOGIN.
# @SIDE_EFFECT Molecular CoT: REASON on entry, REFLECT on success, EXPLORE on failure.
# @RELATION CALLS -> [auth_service]
# @RELATION CALLS -> [Services.Auth.Service]
# @TEST_EDGE: invalid_credentials -> 401
# @TEST_EDGE: locked_account -> 423
# @TEST_EDGE: missing_fields -> 422
@@ -87,7 +87,7 @@ async def login_for_access_token(
# @PRE Valid JWT token in Authorization header.
# @POST Returns UserSchema with id, username, email, roles.
# @SIDE_EFFECT Reads current user from DB via auth middleware.
# @RELATION DEPENDS_ON -> [get_current_user]
# @RELATION DEPENDS_ON -> [Dependencies.AppDependencies.GetCurrentUser]
@router.get("/me", response_model=UserSchema)
async def read_users_me(current_user: UserSchema = Depends(get_current_user)):
@@ -105,7 +105,7 @@ async def read_users_me(current_user: UserSchema = Depends(get_current_user)):
# @POST Token added to blacklist; subsequent requests with same token rejected.
# @SIDE_EFFECT Writes security event LOGOUT; writes to token_blacklist table.
# @SIDE_EFFECT Molecular CoT: REASON/REFLECT/EXPLORE markers.
# @RELATION DEPENDS_ON -> [get_current_user]
# @RELATION DEPENDS_ON -> [Dependencies.AppDependencies.GetCurrentUser]
# @RELATION CALLS -> [Auth.Jwt.BlacklistToken]
# @TEST_EDGE: already_expired_token -> 200 (idempotent)
@@ -139,7 +139,7 @@ async def logout(
# @BRIEF Initiate ADFS OIDC login flow — redirects user to identity provider.
# @POST Redirects user to ADFS authorization endpoint.
# @SIDE_EFFECT Redirects browser to external OIDC provider.
# @RELATION CALLS -> [AuthOauthModule]
# @RELATION CALLS -> [Core.Oauth.AuthOauthModule]
@router.get("/login/adfs")
async def login_adfs(request: starlette.requests.Request):
@@ -162,7 +162,7 @@ async def login_adfs(request: starlette.requests.Request):
# @POST Provisions user in DB (JIT), creates auth session.
# @SIDE_EFFECT DB write for user provisioning; writes security event LOGIN_ADFS.
# @SIDE_EFFECT Molecular CoT: REASON/REFLECT/EXPLORE markers.
# @RELATION CALLS -> [auth_service]
# @RELATION CALLS -> [Services.Auth.Service]
# @TEST_EDGE: adfs_timeout -> 504
# @TEST_EDGE: invalid_state -> 401

View File

@@ -1,21 +1,21 @@
# #region ApiRoutesModule [C:5] [TYPE Module] [SEMANTICS api, package, router, lazy, import]
# #region Api.Init.ApiRoutesModule [C:5] [TYPE Module] [SEMANTICS api, package, router, lazy, import]
# @defgroup Api Module group.
# @BRIEF Provide lazy route module loading to avoid heavyweight imports during tests.
# @LAYER API
# @RELATION CALLS -> [ApiRoutesGetAttr]
# @RELATION BINDS_TO -> [Route_Group_Contracts]
# @RELATION CALLS -> [Api.Init.ApiRoutesGetAttr]
# @RELATION BINDS_TO -> [Api.Init.RouteGroupContracts]
# @PRE FastAPI app initialized, route modules available in package
# @POST Route modules are lazily loadable via __getattr__
# @INVARIANT Only names listed in __all__ are importable via __getattr__.
# #region Route_Group_Contracts [C:3] [TYPE Block]
# #region Api.Init.RouteGroupContracts [C:3] [TYPE Block]
# @ingroup Api
# @BRIEF Declare the canonical route-module registry used by lazy imports and app router inclusion.
# @RELATION DEPENDS_ON -> [PluginsRouter]
# @RELATION DEPENDS_ON -> [TasksRouter]
# @RELATION DEPENDS_ON -> [SettingsRouter]
# @RELATION DEPENDS_ON -> [ReportsRouter]
# @RELATION DEPENDS_ON -> [LlmRoutes]
# @RELATION DEPENDS_ON -> [Api.Plugins.PluginsRouter]
# @RELATION DEPENDS_ON -> [Api.Tasks.TasksRouter]
# @RELATION DEPENDS_ON -> [Api.Settings.SettingsRouter]
# @RELATION DEPENDS_ON -> [Api.Reports.ReportsRouter]
# @RELATION DEPENDS_ON -> [Api.Llm.LlmRoutes]
# @SIDE_EFFECT Registers route group imports via __getattr__
# @DATA_CONTRACT Package -> RouterModule mapping
__all__ = [
@@ -47,13 +47,13 @@ __all__ = [
"translate",
"validation_tasks",
]
# #endregion Route_Group_Contracts
# #endregion Api.Init.RouteGroupContracts
# #region ApiRoutesGetAttr [C:3] [TYPE Function]
# #region Api.Init.ApiRoutesGetAttr [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Lazily import route module by attribute name.
# @RELATION DEPENDS_ON -> [ApiRoutesModule]
# @RELATION DEPENDS_ON -> [Api.Init.ApiRoutesModule]
# @PRE name is module candidate exposed in __all__.
# @POST Returns imported submodule or raises AttributeError.
def __getattr__(name):
@@ -64,5 +64,5 @@ def __getattr__(name):
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# #endregion ApiRoutesGetAttr
# #endregion ApiRoutesModule
# #endregion Api.Init.ApiRoutesGetAttr
# #endregion Api.Init.ApiRoutesModule

View File

@@ -1,4 +1,4 @@
# #region RoutesTestsConftest [TYPE Module] [C:1] [SEMANTICS test, fixture, mock, conftest]
# #region Api.Conftest.RoutesTestsConftest [TYPE Module] [C:1] [SEMANTICS test, fixture, mock, conftest]
# @BRIEF Shared low-fidelity test doubles for API route test modules.
# Set required env vars before any app imports
@@ -37,4 +37,4 @@ class FakeQuery:
return list(self._rows)
def count(self):
return len(self._rows)
# #endregion RoutesTestsConftest
# #endregion Api.Conftest.RoutesTestsConftest

View File

@@ -2,7 +2,7 @@
# #region Test.Api.AgentLifecycle [C:3] [TYPE Module] [SEMANTICS test,agent,lifecycle,api,crud]
# @BRIEF Integration tests for agent lifecycle event API (write + read, user-scoped).
# @RELATION BINDS_TO -> [Api.AgentLifecycle]
# @RELATION BINDS_TO -> [AgentLifecycleService]
# @RELATION BINDS_TO -> [Services.AgentLifecycleService]
# @RELATION BINDS_TO -> [Schemas.AgentLifecycle]
# @INVARIANT Non-admin users see only their own events.
# @INVARIANT Payload is validated against SAFE_PAYLOAD_KEYS — sensitive fields stripped.
@@ -92,7 +92,7 @@ def user_client():
return tc
# #region test_lifecycle_event_write_success [C:2] [TYPE Function] [SEMANTICS test,lifecycle,write]
# #region Test.Api.TestLifecycleEventWriteSuccess [C:2] [TYPE Function] [SEMANTICS test,lifecycle,write]
# @BRIEF POST /api/agent/events creates an event and returns 201 with event ID.
def test_lifecycle_event_write_success(admin_client):
"""POST /api/agent/events with valid body returns 201 and event ID."""
@@ -112,10 +112,10 @@ def test_lifecycle_event_write_success(admin_client):
data = response.json()
assert data["written"] is True
assert len(data["id"]) > 0
# #endregion test_lifecycle_event_write_success
# #endregion Test.Api.TestLifecycleEventWriteSuccess
# #region test_lifecycle_event_write_reduces_payload [C:2] [TYPE Function] [SEMANTICS test,lifecycle,write,payload]
# #region Test.Api.TestLifecycleEventWriteReducesPayload [C:2] [TYPE Function] [SEMANTICS test,lifecycle,write,payload]
# @BRIEF POST /api/agent/events strips sensitive fields from payload.
def test_lifecycle_event_write_reduces_payload(admin_client):
"""Sensitive fields in payload are stripped before storage."""
@@ -147,10 +147,10 @@ def test_lifecycle_event_write_reduces_payload(admin_client):
assert "token" not in event.payload
finally:
session.close()
# #endregion test_lifecycle_event_write_reduces_payload
# #endregion Test.Api.TestLifecycleEventWriteReducesPayload
# #region test_lifecycle_failure_persists_safe_provider_diagnostics [C:2] [TYPE Function] [SEMANTICS test,lifecycle,llm,diagnostics]
# #region Test.Api.TestLifecycleFailurePersistsSafeProviderDiagnostics [C:2] [TYPE Function] [SEMANTICS test,lifecycle,llm,diagnostics]
# @BRIEF LLM failures retain provider identity and failure classification while removing credentials.
def test_lifecycle_failure_persists_safe_provider_diagnostics(admin_client):
tc, session_factory = admin_client
@@ -182,10 +182,10 @@ def test_lifecycle_failure_persists_safe_provider_diagnostics(admin_client):
}
finally:
session.close()
# #endregion test_lifecycle_failure_persists_safe_provider_diagnostics
# #endregion Test.Api.TestLifecycleFailurePersistsSafeProviderDiagnostics
# #region test_lifecycle_event_list_user_scoped [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,scope]
# #region Test.Api.TestLifecycleEventListUserScoped [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,scope]
# @BRIEF GET /api/agent/events — non-admin user sees only own events.
def test_lifecycle_event_list_user_scoped(user_client, admin_client):
"""Non-admin user can list their own events."""
@@ -207,10 +207,10 @@ def test_lifecycle_event_list_user_scoped(user_client, admin_client):
# Regular user should see 0 events (none belong to them)
assert data["total"] == 0
assert data["items"] == []
# #endregion test_lifecycle_event_list_user_scoped
# #endregion Test.Api.TestLifecycleEventListUserScoped
# #region test_lifecycle_event_list_admin_cross_user [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,admin]
# #region Test.Api.TestLifecycleEventListAdminCrossUser [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,admin]
# @BRIEF GET /api/agent/events — admin user can query by user_id.
def test_lifecycle_event_list_admin_cross_user(admin_client):
"""Admin user can query events by user_id filter."""
@@ -253,10 +253,10 @@ def test_lifecycle_event_list_admin_cross_user(admin_client):
data = response.json()
assert data["total"] == 1
assert data["items"][0]["trace_id"] == "trace-2"
# #endregion test_lifecycle_event_list_admin_cross_user
# #endregion Test.Api.TestLifecycleEventListAdminCrossUser
# #region test_lifecycle_event_non_admin_cannot_query_others [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,forbidden]
# #region Test.Api.TestLifecycleEventNonAdminCannotQueryOthers [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,forbidden]
# @BRIEF Non-admin user receives 403 when trying to filter by user_id.
def test_lifecycle_event_non_admin_cannot_query_others(user_client):
"""Non-admin user gets 403 for user_id filter."""
@@ -264,10 +264,10 @@ def test_lifecycle_event_non_admin_cannot_query_others(user_client):
response = tc.get("/api/agent/events?user_id=other-user")
assert response.status_code == 403, response.text
assert "Only admin users" in response.text
# #endregion test_lifecycle_event_non_admin_cannot_query_others
# #endregion Test.Api.TestLifecycleEventNonAdminCannotQueryOthers
# #region test_lifecycle_event_list_pagination [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,pagination]
# #region Test.Api.TestLifecycleEventListPagination [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,pagination]
# @BRIEF GET /api/agent/events returns paginated results with has_next.
def test_lifecycle_event_list_pagination(admin_client):
"""Pagination: page_size respected, has_next computed correctly."""
@@ -301,10 +301,10 @@ def test_lifecycle_event_list_pagination(admin_client):
data = response.json()
assert len(data["items"]) == 2
assert data["has_next"] is False
# #endregion test_lifecycle_event_list_pagination
# #endregion Test.Api.TestLifecycleEventListPagination
# #region test_lifecycle_event_write_requires_auth [C:2] [TYPE Function] [SEMANTICS test,lifecycle,write,auth]
# #region Test.Api.TestLifecycleEventWriteRequiresAuth [C:2] [TYPE Function] [SEMANTICS test,lifecycle,write,auth]
# @BRIEF POST /api/agent/events requires valid authentication token.
def test_lifecycle_event_write_requires_auth():
"""POST without auth returns 401/403 (depends on oauth2_scheme)."""
@@ -319,10 +319,10 @@ def test_lifecycle_event_write_requires_auth():
"event_type": "AGENT_REQUEST_STARTED",
})
assert response.status_code == 401, response.text
# #endregion test_lifecycle_event_write_requires_auth
# #endregion Test.Api.TestLifecycleEventWriteRequiresAuth
# #region test_lifecycle_event_list_filters [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,filters]
# #region Test.Api.TestLifecycleEventListFilters [C:2] [TYPE Function] [SEMANTICS test,lifecycle,list,filters]
# @BRIEF GET /api/agent/events supports conversation_id, status, tool_name filters.
def test_lifecycle_event_list_filters(admin_client):
"""All query parameter filters work correctly."""
@@ -362,5 +362,5 @@ def test_lifecycle_event_list_filters(admin_client):
resp = tc.get("/api/agent/events?tool_name=deploy")
assert resp.status_code == 200
assert resp.json()["total"] == 1
# #endregion test_lifecycle_event_list_filters
# #endregion Test.Api.TestLifecycleEventListFilters
# #endregion Test.Api.AgentLifecycle

View File

@@ -1,9 +1,9 @@
import os
os.environ["ENCRYPTION_KEY"] = "OnrCzomBWbIjTf7Y-fnhL2adlU55bHZQjp8zX5zBC5w="
# #region AssistantApiTests [TYPE Module] [C:3] [SEMANTICS tests, assistant, api]
# #region Test.Tests.AssistantApiTests [TYPE Module] [C:3] [SEMANTICS tests, assistant, api]
# @BRIEF Validate assistant API endpoint logic via direct async handler invocation.
# @RELATION DEPENDS_ON -> [AssistantApi]
# @RELATION DEPENDS_ON -> [Api.Init.AssistantApi]
# @INVARIANT Every test clears assistant in-memory state before execution.
import asyncio
from datetime import UTC, datetime
@@ -15,13 +15,13 @@ from src.models.assistant import AssistantMessageRecord
from src.schemas.auth import User
# #region _run_async [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #region Test.Tests.RunAsync [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
def _run_async(coro):
return asyncio.run(coro)
# #endregion _run_async
# #region _FakeTask [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #endregion Test.Tests.RunAsync
# #region Test.Tests.FakeTask [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF Lightweight task model stub used as return value from _FakeTaskManager.create_task in assistant route tests.
# @INVARIANT status is a bare string not a TaskStatus enum; callers must not depend on enum semantics.
class _FakeTask:
@@ -42,10 +42,10 @@ class _FakeTask:
self.user_id = user_id
self.started_at = datetime.now(UTC)
self.finished_at = datetime.now(UTC)
# #endregion _FakeTask
# #endregion Test.Tests.FakeTask
# @DEBT: Divergent _FakeTaskManager definition. Canonical version should be in conftest.py. Authz variant is missing get_all_tasks().
# #region _FakeTaskManager [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #region Test.Tests.FakeTaskManager [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF In-memory task manager stub that records created tasks for route-level assertions.
# @INVARIANT create_task stores tasks retrievable by get_task/get_tasks without external side effects.
class _FakeTaskManager:
@@ -70,9 +70,9 @@ class _FakeTaskManager:
]
def get_all_tasks(self):
return list(self.tasks.values())
# #endregion _FakeTaskManager
# #region _FakeConfigManager [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #endregion Test.Tests.FakeTaskManager
# #region Test.Tests.FakeConfigManager [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF Deterministic config stub providing hardcoded dev/prod environments and minimal settings shape for assistant route tests.
# @INVARIANT get_config() returns anonymous inner classes, not real GlobalSettings; only default_environment_id and llm fields are safe to access.
class _FakeConfigManager:
@@ -90,9 +90,9 @@ class _FakeConfigManager:
settings = _Settings()
environments = []
return _Config()
# #endregion _FakeConfigManager
# #region _admin_user [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #endregion Test.Tests.FakeConfigManager
# #region Test.Tests.AdminUser [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF Build admin principal with spec=User for assistant route authorization tests.
def _admin_user():
user = MagicMock(spec=User)
@@ -102,9 +102,9 @@ def _admin_user():
role.name = "Admin"
user.roles = [role]
return user
# #endregion _admin_user
# #region _limited_user [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #endregion Test.Tests.AdminUser
# #region Test.Tests.LimitedUser [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF Build limited user principal with empty roles for assistant route denial tests.
def _limited_user():
user = MagicMock(spec=User)
@@ -112,9 +112,9 @@ def _limited_user():
user.username = "limited"
user.roles = []
return user
# #endregion _limited_user
# #region _FakeQuery [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #endregion Test.Tests.LimitedUser
# #region Test.Tests.FakeQuery [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF Chainable SQLAlchemy-like query stub returning fixed item lists for assistant message persistence paths.
# @INVARIANT filter() ignores all predicate arguments and returns self; no predicate-based filtering is emulated.
class _FakeQuery:
@@ -141,9 +141,9 @@ class _FakeQuery:
return self.items
def count(self):
return len(self.items)
# #endregion _FakeQuery
# #region _FakeDb [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #endregion Test.Tests.FakeQuery
# #region Test.Tests.FakeDb [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF Explicit in-memory DB session double limited to assistant message persistence paths.
# @INVARIANT query() always returns _FakeQuery with intentionally non-evaluated predicates; add/merge stay deterministic and never emulate unrelated SQLAlchemy behavior.
class _FakeDb:
@@ -163,24 +163,24 @@ class _FakeDb:
return obj
def refresh(self, obj):
pass
# #endregion _FakeDb
# #region _clear_assistant_state [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #endregion Test.Tests.FakeDb
# #region Test.Tests.ClearAssistantState [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
def _clear_assistant_state():
assistant_routes.CONVERSATIONS.clear()
assistant_routes.USER_ACTIVE_CONVERSATION.clear()
assistant_routes.CONFIRMATIONS.clear()
assistant_routes.ASSISTANT_AUDIT.clear()
# #endregion _clear_assistant_state
# #endregion Test.Tests.ClearAssistantState
# #region _await_none [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #region Test.Tests.AwaitNone [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF Async helper returning None for planner fallback tests.
async def _await_none(*args, **kwargs):
return None
# #endregion _await_none
# #region test_unknown_command_returns_needs_clarification [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #endregion Test.Tests.AwaitNone
# #region Test.Tests.TestUnknownCommandReturnsNeedsClarification [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF Unknown command should return clarification state and unknown intent.
def test_unknown_command_returns_needs_clarification(monkeypatch):
_clear_assistant_state()
@@ -198,9 +198,9 @@ def test_unknown_command_returns_needs_clarification(monkeypatch):
)
assert resp.state == "needs_clarification"
assert "уточните" in resp.text.lower() or "неоднозначна" in resp.text.lower()
# #endregion test_unknown_command_returns_needs_clarification
# #region test_capabilities_question_returns_successful_help [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# #endregion Test.Tests.TestUnknownCommandReturnsNeedsClarification
# #region Test.Tests.TestCapabilitiesQuestionReturnsSuccessfulHelp [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.AssistantApiTests]
# @BRIEF Capability query should return deterministic help response.
def test_capabilities_question_returns_successful_help(monkeypatch):
_clear_assistant_state()
@@ -228,5 +228,5 @@ def test_capabilities_question_returns_successful_help(monkeypatch):
)
assert resp.state == "success"
assert "я могу сделать" in resp.text.lower()
# #endregion test_capabilities_question_returns_successful_help
# #endregion AssistantApiTests
# #endregion Test.Tests.TestCapabilitiesQuestionReturnsSuccessfulHelp
# #endregion Test.Tests.AssistantApiTests

View File

@@ -1,10 +1,10 @@
import os
os.environ["ENCRYPTION_KEY"] = "OnrCzomBWbIjTf7Y-fnhL2adlU55bHZQjp8zX5zBC5w="
# #region TestAssistantAuthz [TYPE Module] [C:3] [SEMANTICS tests, assistant, authz, confirmation, rbac]
# #region Test.Tests.TestAssistantAuthz [TYPE Module] [C:3] [SEMANTICS tests, assistant, authz, confirmation, rbac]
# @BRIEF Verify assistant confirmation ownership, expiration, and deny behavior for restricted users.
# @LAYER API
# @RELATION DEPENDS_ON -> AssistantApi
# @RELATION DEPENDS_ON -> Api.Init.AssistantApi
# @INVARIANT Security-sensitive flows fail closed for unauthorized actors.
import asyncio
from datetime import UTC, datetime, timedelta
@@ -30,16 +30,16 @@ from src.models.assistant import (
)
# #region _run_async [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #region Test.Tests.RunAsync [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Execute async endpoint handler in synchronous test context.
# @PRE coroutine is awaitable endpoint invocation.
# @POST Returns coroutine result or raises propagated exception.
def _run_async(coroutine):
return asyncio.run(coroutine)
# #endregion _run_async
# #region _FakeTask [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.RunAsync
# #region Test.Tests.FakeTask [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Lightweight task model used for assistant authz tests.
# @PRE task_id is non-empty string.
# @POST Returns task with provided id, status, and user_id accessible as attributes.
@@ -48,10 +48,10 @@ class _FakeTask:
self.id = task_id
self.status = status
self.user_id = user_id
# #endregion _FakeTask
# #endregion Test.Tests.FakeTask
# @DEBT: Divergent _FakeTaskManager definition. Canonical version should be in conftest.py. Authz variant is missing get_all_tasks().
# #region _FakeTaskManager [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #region Test.Tests.FakeTaskManager [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF In-memory task manager double that records assistant-created tasks deterministically.
# @INVARIANT Only create_task/get_task/get_tasks behavior used by assistant authz routes is emulated.
class _FakeTaskManager:
@@ -73,10 +73,10 @@ class _FakeTaskManager:
raise NotImplementedError(
"get_all_tasks not implemented in authz FakeTaskManager"
)
# #endregion _FakeTaskManager
# #endregion Test.Tests.FakeTaskManager
# @CONTRACT: Partial ConfigManager stub for authz tests. Missing: get_config().
# #region _FakeConfigManager [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #region Test.Tests.FakeConfigManager [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Provide deterministic environment aliases required by intent parsing.
# @PRE No external config or DB state is required.
# @POST get_environments() returns two deterministic SimpleNamespace stubs with id/name.
@@ -91,36 +91,36 @@ class _FakeConfigManager:
raise NotImplementedError(
"get_config not implemented in authz fake — add if route under test requires it"
)
# #endregion _FakeConfigManager
# #region _admin_user [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.FakeConfigManager
# #region Test.Tests.AdminUser [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Build admin principal fixture.
# @PRE Test requires privileged principal for risky operations.
# @POST Returns admin-like user stub with Admin role.
def _admin_user():
role = SimpleNamespace(name="Admin", permissions=[])
return SimpleNamespace(id="u-admin", username="admin", roles=[role])
# #endregion _admin_user
# #region _other_admin_user [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.AdminUser
# #region Test.Tests.OtherAdminUser [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Build second admin principal fixture for ownership tests.
# @PRE Ownership mismatch scenario needs distinct authenticated actor.
# @POST Returns alternate admin-like user stub.
def _other_admin_user():
role = SimpleNamespace(name="Admin", permissions=[])
return SimpleNamespace(id="u-admin-2", username="admin2", roles=[role])
# #endregion _other_admin_user
# #region _limited_user [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.OtherAdminUser
# #region Test.Tests.LimitedUser [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Build limited principal without required assistant execution privileges.
# @PRE Permission denial scenario needs non-admin actor.
# @POST Returns restricted user stub.
def _limited_user():
role = SimpleNamespace(name="Operator", permissions=[])
return SimpleNamespace(id="u-limited", username="limited", roles=[role])
# #endregion _limited_user
# #region _FakeQuery [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.LimitedUser
# #region Test.Tests.FakeQuery [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Minimal chainable query object for fake DB interactions.
# @INVARIANT filter() deliberately discards predicate args and returns self; tests must not assume predicate evaluation.
class _FakeQuery:
@@ -143,9 +143,9 @@ class _FakeQuery:
return self
def count(self):
return len(self._rows)
# #endregion _FakeQuery
# #region _FakeDb [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.FakeQuery
# #region Test.Tests.FakeDb [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF In-memory DB session double constrained to assistant message/confirmation/audit persistence paths.
# @INVARIANT query/add/merge are intentionally narrow and must not claim full SQLAlchemy Session semantics.
class _FakeDb:
@@ -183,9 +183,9 @@ class _FakeDb:
return None
def rollback(self):
return None
# #endregion _FakeDb
# #region _clear_assistant_state [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.FakeDb
# #region Test.Tests.ClearAssistantState [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Reset assistant process-local state between test cases.
# @PRE Assistant globals may contain state from prior tests.
# @POST Assistant in-memory state dictionaries are cleared.
@@ -194,9 +194,9 @@ def _clear_assistant_state():
assistant_module.USER_ACTIVE_CONVERSATION.clear()
assistant_module.CONFIRMATIONS.clear()
assistant_module.ASSISTANT_AUDIT.clear()
# #endregion _clear_assistant_state
# #region test_confirmation_owner_mismatch_returns_403 [TYPE Function]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.ClearAssistantState
# #region Test.Tests.TestConfirmationOwnerMismatchReturns403 [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Confirm endpoint should reject requests from user that does not own the confirmation token.
# @PRE Confirmation token is created by first admin actor.
# @POST Second actor receives 403 on confirm operation.
@@ -227,9 +227,9 @@ def test_confirmation_owner_mismatch_returns_403():
)
)
assert exc.value.status_code == 403
# #endregion test_confirmation_owner_mismatch_returns_403
# #region test_expired_confirmation_cannot_be_confirmed [TYPE Function]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.TestConfirmationOwnerMismatchReturns403
# #region Test.Tests.TestExpiredConfirmationCannotBeConfirmed [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Expired confirmation token should be rejected and not create task.
# @PRE Confirmation token exists and is manually expired before confirm request.
# @POST Confirm endpoint raises 400 and no task is created.
@@ -263,9 +263,9 @@ def test_expired_confirmation_cannot_be_confirmed():
)
assert exc.value.status_code == 400
assert task_manager.get_tasks(limit=10, offset=0) == []
# #endregion test_expired_confirmation_cannot_be_confirmed
# #region test_limited_user_cannot_launch_restricted_operation [TYPE Function]
# @RELATION BINDS_TO -> [TestAssistantAuthz]
# #endregion Test.Tests.TestExpiredConfirmationCannotBeConfirmed
# #region Test.Tests.TestLimitedUserCannotLaunchRestrictedOperation [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestAssistantAuthz]
# @BRIEF Limited user should receive denied state for privileged operation.
# @PRE Restricted user attempts dangerous deploy command.
# @POST Assistant returns denied state and does not execute operation.
@@ -283,5 +283,5 @@ def test_limited_user_cannot_launch_restricted_operation():
)
)
assert response.state == "denied"
# #endregion test_limited_user_cannot_launch_restricted_operation
# #endregion TestAssistantAuthz
# #endregion Test.Tests.TestLimitedUserCannotLaunchRestrictedOperation
# #endregion Test.Tests.TestAssistantAuthz

View File

@@ -1,5 +1,5 @@
# #region TestCleanReleaseApi [TYPE Module] [C:3] [SEMANTICS tests, api, clean-release, checks, reports]
# @RELATION BINDS_TO -> SrcRoot
# #region Test.Tests.TestCleanReleaseApi [TYPE Module] [C:3] [SEMANTICS tests, api, clean-release, checks, reports]
# @RELATION BINDS_TO -> Init.SrcRoot
# @BRIEF Contract tests for clean release checks and reports endpoints.
# @LAYER Domain
# @INVARIANT API returns deterministic payload shapes for checks and reports.
@@ -22,8 +22,8 @@ from src.models.clean_release import (
from src.services.clean_release.repository import CleanReleaseRepository
# #region _repo_with_seed_data [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseApi
# #region Test.Tests.RepoWithSeedData [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseApi
def _repo_with_seed_data() -> CleanReleaseRepository:
repo = CleanReleaseRepository()
repo.save_candidate(
@@ -69,9 +69,9 @@ def _repo_with_seed_data() -> CleanReleaseRepository:
)
)
return repo
# #endregion _repo_with_seed_data
# #region test_start_check_and_get_status_contract [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseApi
# #endregion Test.Tests.RepoWithSeedData
# #region Test.Tests.TestStartCheckAndGetStatusContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseApi
# @BRIEF Validate checks start endpoint returns expected identifiers and status endpoint reflects the same run.
def test_start_check_and_get_status_contract():
repo = _repo_with_seed_data()
@@ -101,9 +101,9 @@ def test_start_check_and_get_status_contract():
assert "checks" in status_payload
finally:
app.dependency_overrides.clear()
# #endregion test_start_check_and_get_status_contract
# #region test_get_report_not_found_returns_404 [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseApi
# #endregion Test.Tests.TestStartCheckAndGetStatusContract
# #region Test.Tests.TestGetReportNotFoundReturns404 [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseApi
# @BRIEF Validate reports endpoint returns 404 for an unknown report identifier.
def test_get_report_not_found_returns_404():
repo = _repo_with_seed_data()
@@ -114,9 +114,9 @@ def test_get_report_not_found_returns_404():
assert resp.status_code == 404
finally:
app.dependency_overrides.clear()
# #endregion test_get_report_not_found_returns_404
# #region test_get_report_success [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseApi
# #endregion Test.Tests.TestGetReportNotFoundReturns404
# #region Test.Tests.TestGetReportSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseApi
# @BRIEF Validate reports endpoint returns persisted report payload for an existing report identifier.
def test_get_report_success():
repo = _repo_with_seed_data()
@@ -140,9 +140,9 @@ def test_get_report_success():
assert resp.json()["report_id"] == "rep-1"
finally:
app.dependency_overrides.clear()
# #endregion test_get_report_success
# #region test_prepare_candidate_api_success [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseApi
# #endregion Test.Tests.TestGetReportSuccess
# #region Test.Tests.TestPrepareCandidateApiSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseApi
# @BRIEF Validate candidate preparation endpoint returns prepared status and manifest identifier on valid input.
def test_prepare_candidate_api_success():
repo = _repo_with_seed_data()
@@ -166,5 +166,5 @@ def test_prepare_candidate_api_success():
assert "manifest_id" in data
finally:
app.dependency_overrides.clear()
# #endregion test_prepare_candidate_api_success
# #endregion TestCleanReleaseApi
# #endregion Test.Tests.TestPrepareCandidateApiSuccess
# #endregion Test.Tests.TestCleanReleaseApi

View File

@@ -1,5 +1,5 @@
# #region TestCleanReleaseLegacyCompat [TYPE Module] [C:3] [SEMANTICS test, clean-release, legacy, compat]
# @RELATION BINDS_TO -> SrcRoot
# #region Test.Tests.TestCleanReleaseLegacyCompat [TYPE Module] [C:3] [SEMANTICS test, clean-release, legacy, compat]
# @RELATION BINDS_TO -> Init.SrcRoot
# @BRIEF Compatibility tests for legacy clean-release API paths retained during v2 migration.
# @LAYER Tests
from __future__ import annotations
@@ -27,8 +27,8 @@ from src.models.clean_release import (
from src.services.clean_release.repository import CleanReleaseRepository
# #region _seed_legacy_repo [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseLegacyCompat
# #region Test.Tests.SeedLegacyRepo [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseLegacyCompat
# @BRIEF Seed in-memory repository with minimum trusted data for legacy endpoint contracts.
# @PRE Repository is empty.
# @POST Candidate, policy, registry and manifest are available for legacy checks flow.
@@ -97,9 +97,9 @@ def _seed_legacy_repo() -> CleanReleaseRepository:
)
)
return repo
# #endregion _seed_legacy_repo
# #region test_legacy_prepare_endpoint_still_available [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseLegacyCompat
# #endregion Test.Tests.SeedLegacyRepo
# #region Test.Tests.TestLegacyPrepareEndpointStillAvailable [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseLegacyCompat
# @BRIEF Verify legacy prepare endpoint remains reachable and returns a status payload.
def test_legacy_prepare_endpoint_still_available() -> None:
repo = _seed_legacy_repo()
@@ -123,9 +123,9 @@ def test_legacy_prepare_endpoint_still_available() -> None:
assert payload["status"] in {"prepared", "blocked", "PREPARED", "BLOCKED"}
finally:
app.dependency_overrides.clear()
# #endregion test_legacy_prepare_endpoint_still_available
# #region test_legacy_checks_endpoints_still_available [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseLegacyCompat
# #endregion Test.Tests.TestLegacyPrepareEndpointStillAvailable
# #region Test.Tests.TestLegacyChecksEndpointsStillAvailable [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseLegacyCompat
# @BRIEF Verify legacy checks start/status endpoints remain available during v2 transition.
def test_legacy_checks_endpoints_still_available() -> None:
repo = _seed_legacy_repo()
@@ -155,5 +155,5 @@ def test_legacy_checks_endpoints_still_available() -> None:
assert "checks" in status_payload
finally:
app.dependency_overrides.clear()
# #endregion test_legacy_checks_endpoints_still_available
# #endregion TestCleanReleaseLegacyCompat
# #endregion Test.Tests.TestLegacyChecksEndpointsStillAvailable
# #endregion Test.Tests.TestCleanReleaseLegacyCompat

View File

@@ -1,5 +1,5 @@
# #region TestCleanReleaseSourcePolicy [TYPE Module] [C:3] [SEMANTICS tests, api, clean-release, source-policy]
# @RELATION BINDS_TO -> SrcRoot
# #region Test.Tests.TestCleanReleaseSourcePolicy [TYPE Module] [C:3] [SEMANTICS tests, api, clean-release, source-policy]
# @RELATION BINDS_TO -> Init.SrcRoot
# @BRIEF Validate API behavior for source isolation violations in clean release preparation.
# @LAYER Domain
# @INVARIANT External endpoints must produce blocking violation entries.
@@ -20,8 +20,8 @@ from src.models.clean_release import (
from src.services.clean_release.repository import CleanReleaseRepository
# #region _repo_with_seed_data [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseSourcePolicy
# #region Test.Tests.RepoWithSeedData [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseSourcePolicy
# @BRIEF Seed repository with candidate, registry, and active policy for source isolation test flow.
def _repo_with_seed_data() -> CleanReleaseRepository:
repo = CleanReleaseRepository()
@@ -68,9 +68,9 @@ def _repo_with_seed_data() -> CleanReleaseRepository:
)
)
return repo
# #endregion _repo_with_seed_data
# #region test_prepare_candidate_blocks_external_source [TYPE Function]
# @RELATION BINDS_TO -> TestCleanReleaseSourcePolicy
# #endregion Test.Tests.RepoWithSeedData
# #region Test.Tests.TestPrepareCandidateBlocksExternalSource [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestCleanReleaseSourcePolicy
# @BRIEF Verify candidate preparation is blocked when at least one source host is external to the trusted registry.
def test_prepare_candidate_blocks_external_source():
repo = _repo_with_seed_data()
@@ -98,5 +98,5 @@ def test_prepare_candidate_blocks_external_source():
assert any(v["category"] == "external-source" for v in data["violations"])
finally:
app.dependency_overrides.clear()
# #endregion test_prepare_candidate_blocks_external_source
# #endregion TestCleanReleaseSourcePolicy
# #endregion Test.Tests.TestPrepareCandidateBlocksExternalSource
# #endregion Test.Tests.TestCleanReleaseSourcePolicy

View File

@@ -1,7 +1,7 @@
# #region CleanReleaseV2ApiTests [TYPE Module] [C:3] [SEMANTICS test, clean-release, v2, api, contract]
# #region Test.Tests.CleanReleaseV2ApiTests [TYPE Module] [C:3] [SEMANTICS test, clean-release, v2, api, contract]
# @BRIEF API contract tests for redesigned clean release endpoints.
# @LAYER Domain
# @RELATION DEPENDS_ON -> [CleanReleaseV2Api]
# @RELATION DEPENDS_ON -> [Api.CleanReleaseV2.CleanReleaseV2Api]
from fastapi.testclient import TestClient
from src.app import app
@@ -9,8 +9,8 @@ from src.services.clean_release.enums import CandidateStatus
client = TestClient(app)
# [REASON] Implementing API contract tests for candidate/artifact/manifest endpoints (T012).
# #region test_candidate_registration_contract [TYPE Function]
# @RELATION BINDS_TO -> CleanReleaseV2ApiTests
# #region Test.Tests.TestCandidateRegistrationContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.CleanReleaseV2ApiTests
# @BRIEF Validate candidate registration endpoint creates a draft candidate with expected identifier contract.
def test_candidate_registration_contract():
"""
@@ -28,9 +28,9 @@ def test_candidate_registration_contract():
data = response.json()
assert data["id"] == "rc-test-001"
assert data["status"] == CandidateStatus.DRAFT.value
# #endregion test_candidate_registration_contract
# #region test_artifact_import_contract [TYPE Function]
# @RELATION BINDS_TO -> CleanReleaseV2ApiTests
# #endregion Test.Tests.TestCandidateRegistrationContract
# #region Test.Tests.TestArtifactImportContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.CleanReleaseV2ApiTests
# @BRIEF Validate artifact import endpoint accepts candidate artifacts and returns success status payload.
def test_artifact_import_contract():
"""
@@ -58,9 +58,9 @@ def test_artifact_import_contract():
)
assert response.status_code == 200
assert response.json()["status"] == "success"
# #endregion test_artifact_import_contract
# #region test_manifest_build_contract [TYPE Function]
# @RELATION BINDS_TO -> CleanReleaseV2ApiTests
# #endregion Test.Tests.TestArtifactImportContract
# #region Test.Tests.TestManifestBuildContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.CleanReleaseV2ApiTests
# @BRIEF Validate manifest build endpoint produces manifest payload linked to the target candidate.
def test_manifest_build_contract():
"""
@@ -83,5 +83,5 @@ def test_manifest_build_contract():
data = response.json()
assert "manifest_digest" in data
assert data["candidate_id"] == candidate_id
# #endregion test_manifest_build_contract
# #endregion CleanReleaseV2ApiTests
# #endregion Test.Tests.TestManifestBuildContract
# #endregion Test.Tests.CleanReleaseV2ApiTests

View File

@@ -1,7 +1,7 @@
# #region CleanReleaseV2ReleaseApiTests [TYPE Module] [C:3] [SEMANTICS test, clean-release, release, approval, publication]
# #region Test.Tests.CleanReleaseV2ReleaseApiTests [TYPE Module] [C:3] [SEMANTICS test, clean-release, release, approval, publication]
# @BRIEF API contract test scaffolding for clean release approval and publication endpoints.
# @LAYER Domain
# @RELATION DEPENDS_ON -> [CleanReleaseV2Api]
# @RELATION DEPENDS_ON -> [Api.CleanReleaseV2.CleanReleaseV2Api]
"""Contract tests for redesigned approval/publication API endpoints."""
from datetime import UTC, datetime
from uuid import uuid4
@@ -17,8 +17,8 @@ from src.services.clean_release.enums import CandidateStatus, ComplianceDecision
test_app = FastAPI()
test_app.include_router(clean_release_v2_router)
client = TestClient(test_app)
# #region _seed_candidate_and_passed_report [TYPE Function]
# @RELATION BINDS_TO -> CleanReleaseV2ReleaseApiTests
# #region Test.Tests.SeedCandidateAndPassedReport [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.CleanReleaseV2ReleaseApiTests
# @BRIEF Seed repository with approvable candidate and passed report for release endpoint contracts.
def _seed_candidate_and_passed_report() -> tuple[str, str]:
repository = get_clean_release_repository()
@@ -50,9 +50,9 @@ def _seed_candidate_and_passed_report() -> tuple[str, str]:
)
)
return candidate_id, report_id
# #endregion _seed_candidate_and_passed_report
# #region test_release_approve_and_publish_revoke_contract [TYPE Function]
# @RELATION BINDS_TO -> CleanReleaseV2ReleaseApiTests
# #endregion Test.Tests.SeedCandidateAndPassedReport
# #region Test.Tests.TestReleaseApproveAndPublishRevokeContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.CleanReleaseV2ReleaseApiTests
# @BRIEF Verify approve, publish, and revoke endpoints preserve expected release lifecycle contract.
def test_release_approve_and_publish_revoke_contract() -> None:
"""Contract for approve -> publish -> revoke lifecycle endpoints."""
@@ -87,9 +87,9 @@ def test_release_approve_and_publish_revoke_contract() -> None:
revoke_payload = revoke_response.json()
assert revoke_payload["status"] == "ok"
assert revoke_payload["publication"]["status"] == "REVOKED"
# #endregion test_release_approve_and_publish_revoke_contract
# #region test_release_reject_contract [TYPE Function]
# @RELATION BINDS_TO -> CleanReleaseV2ReleaseApiTests
# #endregion Test.Tests.TestReleaseApproveAndPublishRevokeContract
# #region Test.Tests.TestReleaseRejectContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.CleanReleaseV2ReleaseApiTests
# @BRIEF Verify reject endpoint returns successful rejection decision payload.
def test_release_reject_contract() -> None:
"""Contract for reject endpoint."""
@@ -102,5 +102,5 @@ def test_release_reject_contract() -> None:
payload = reject_response.json()
assert payload["status"] == "ok"
assert payload["decision"] == "REJECTED"
# #endregion test_release_reject_contract
# #endregion CleanReleaseV2ReleaseApiTests
# #endregion Test.Tests.TestReleaseRejectContract
# #endregion Test.Tests.CleanReleaseV2ReleaseApiTests

View File

@@ -1,7 +1,7 @@
# #region DashboardsApiTests [TYPE Module] [C:3] [SEMANTICS test, dashboard, api, listing, migration]
# #region Test.Tests.DashboardsApiTests [TYPE Module] [C:3] [SEMANTICS test, dashboard, api, listing, migration]
# @BRIEF Unit tests for dashboards API endpoints.
# @LAYER API
# @RELATION DEPENDS_ON -> [DashboardsApi]
# @RELATION DEPENDS_ON -> [Api.Init.DashboardsApi]
from datetime import UTC, datetime
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
@@ -61,8 +61,8 @@ def mock_deps():
}
app.dependency_overrides.clear()
client = TestClient(app)
# #region test_get_dashboards_success [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #region Test.Tests.TestGetDashboardsSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboards listing returns a populated response that satisfies the schema contract.
# @TEST: GET /api/dashboards returns 200 and valid schema
# @PRE env_id exists
@@ -95,9 +95,9 @@ def test_get_dashboards_success(mock_deps):
assert data["total"] == 1
assert "page" in data
DashboardsResponse(**data)
# #endregion test_get_dashboards_success
# #region test_get_dashboards_with_search [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsSuccess
# #region Test.Tests.TestGetDashboardsWithSearch [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboards listing applies the search filter and returns only matching rows.
# @TEST: GET /api/dashboards filters by search term
# @PRE search parameter provided
@@ -133,9 +133,9 @@ def test_get_dashboards_with_search(mock_deps):
# @POST Filtered result count must match search
assert len(data["dashboards"]) == 1
assert data["dashboards"][0]["title"] == "Sales Report"
# #endregion test_get_dashboards_with_search
# #region test_get_dashboards_empty [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsWithSearch
# #region Test.Tests.TestGetDashboardsEmpty [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboards listing returns an empty payload for an environment without dashboards.
# @TEST_EDGE empty_dashboards -> {env_id: 'empty_env', expected_total: 0}
def test_get_dashboards_empty(mock_deps):
@@ -152,9 +152,9 @@ def test_get_dashboards_empty(mock_deps):
assert len(data["dashboards"]) == 0
assert data["total_pages"] == 1
DashboardsResponse(**data)
# #endregion test_get_dashboards_empty
# #region test_get_dashboards_superset_failure [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsEmpty
# #region Test.Tests.TestGetDashboardsSupersetFailure [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboards listing surfaces a 503 contract when Superset access fails.
# @TEST_EDGE external_superset_failure -> {env_id: 'bad_conn', status: 503}
def test_get_dashboards_superset_failure(mock_deps):
@@ -169,9 +169,9 @@ def test_get_dashboards_superset_failure(mock_deps):
response = client.get("/api/dashboards?env_id=bad_conn")
assert response.status_code == 503
assert "Failed to fetch dashboards" in response.json()["detail"]
# #endregion test_get_dashboards_superset_failure
# #region test_get_dashboards_env_not_found [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsSupersetFailure
# #region Test.Tests.TestGetDashboardsEnvNotFound [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboards listing returns 404 when the requested environment does not exist.
# @TEST: GET /api/dashboards returns 404 if env_id missing
# @PRE env_id does not exist
@@ -181,9 +181,9 @@ def test_get_dashboards_env_not_found(mock_deps):
response = client.get("/api/dashboards?env_id=nonexistent")
assert response.status_code == 404
assert "Environment not found" in response.json()["detail"]
# #endregion test_get_dashboards_env_not_found
# #region test_get_dashboards_invalid_pagination [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsEnvNotFound
# #region Test.Tests.TestGetDashboardsInvalidPagination [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboards listing rejects invalid pagination parameters with 400 responses.
# @TEST: GET /api/dashboards returns 400 for invalid page/page_size
# @PRE page < 1 or page_size > 100
@@ -200,9 +200,9 @@ def test_get_dashboards_invalid_pagination(mock_deps):
response = client.get("/api/dashboards?env_id=prod&page_size=101")
assert response.status_code == 400
assert "Page size must be between 1 and 100" in response.json()["detail"]
# #endregion test_get_dashboards_invalid_pagination
# #region test_get_dashboard_detail_success [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsInvalidPagination
# #region Test.Tests.TestGetDashboardDetailSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboard detail returns charts and datasets for an existing dashboard.
# @TEST: GET /api/dashboards/{id} returns dashboard detail with charts and datasets
def test_get_dashboard_detail_success(mock_deps):
@@ -249,9 +249,9 @@ def test_get_dashboard_detail_success(mock_deps):
assert payload["id"] == 42
assert payload["chart_count"] == 1
assert payload["dataset_count"] == 1
# #endregion test_get_dashboard_detail_success
# #region test_get_dashboard_detail_env_not_found [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardDetailSuccess
# #region Test.Tests.TestGetDashboardDetailEnvNotFound [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboard detail returns 404 when the requested environment is missing.
# @TEST: GET /api/dashboards/{id} returns 404 for missing environment
def test_get_dashboard_detail_env_not_found(mock_deps):
@@ -259,9 +259,9 @@ def test_get_dashboard_detail_env_not_found(mock_deps):
response = client.get("/api/dashboards/42?env_id=missing")
assert response.status_code == 404
assert "Environment not found" in response.json()["detail"]
# #endregion test_get_dashboard_detail_env_not_found
# #region test_migrate_dashboards_success [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardDetailEnvNotFound
# #region Test.Tests.TestMigrateDashboardsSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: POST /api/dashboards/migrate creates migration task
# @PRE Valid source_env_id, target_env_id, dashboard_ids
# @BRIEF Validate dashboard migration request creates an async task and returns its identifier.
@@ -289,9 +289,9 @@ def test_migrate_dashboards_success(mock_deps):
assert "task_id" in data
# @POST/@SIDE_EFFECT: create_task was called
mock_deps["task"].create_task.assert_called_once()
# #endregion test_migrate_dashboards_success
# #region test_migrate_dashboards_no_ids [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestMigrateDashboardsSuccess
# #region Test.Tests.TestMigrateDashboardsNoIds [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: POST /api/dashboards/migrate returns 400 for empty dashboard_ids
# @PRE dashboard_ids is empty
# @BRIEF Validate dashboard migration rejects empty dashboard identifier lists.
@@ -307,9 +307,9 @@ def test_migrate_dashboards_no_ids(mock_deps):
)
assert response.status_code == 400
assert "At least one dashboard ID must be provided" in response.json()["detail"]
# #endregion test_migrate_dashboards_no_ids
# #region test_migrate_dashboards_env_not_found [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestMigrateDashboardsNoIds
# #region Test.Tests.TestMigrateDashboardsEnvNotFound [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate migration creation returns 404 when the source environment cannot be resolved.
# @PRE source_env_id and target_env_id are valid environment IDs
def test_migrate_dashboards_env_not_found(mock_deps):
@@ -321,9 +321,9 @@ def test_migrate_dashboards_env_not_found(mock_deps):
)
assert response.status_code == 404
assert "Source environment not found" in response.json()["detail"]
# #endregion test_migrate_dashboards_env_not_found
# #region test_backup_dashboards_success [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestMigrateDashboardsEnvNotFound
# #region Test.Tests.TestBackupDashboardsSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: POST /api/dashboards/backup creates backup task
# @PRE Valid env_id, dashboard_ids
# @BRIEF Validate dashboard backup request creates an async backup task and returns its identifier.
@@ -344,9 +344,9 @@ def test_backup_dashboards_success(mock_deps):
assert "task_id" in data
# @POST/@SIDE_EFFECT: create_task was called
mock_deps["task"].create_task.assert_called_once()
# #endregion test_backup_dashboards_success
# #region test_backup_dashboards_env_not_found [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestBackupDashboardsSuccess
# #region Test.Tests.TestBackupDashboardsEnvNotFound [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate backup task creation returns 404 when the target environment is missing.
# @PRE env_id is a valid environment ID
def test_backup_dashboards_env_not_found(mock_deps):
@@ -357,9 +357,9 @@ def test_backup_dashboards_env_not_found(mock_deps):
)
assert response.status_code == 404
assert "Environment not found" in response.json()["detail"]
# #endregion test_backup_dashboards_env_not_found
# #region test_get_database_mappings_success [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestBackupDashboardsEnvNotFound
# #region Test.Tests.TestGetDatabaseMappingsSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: GET /api/dashboards/db-mappings returns mapping suggestions
# @PRE Valid source_env_id, target_env_id
# @BRIEF Validate database mapping suggestions are returned for valid source and target environments.
@@ -389,9 +389,9 @@ def test_get_database_mappings_success(mock_deps):
assert "mappings" in data
assert len(data["mappings"]) == 1
assert data["mappings"][0]["confidence"] == 0.95
# #endregion test_get_database_mappings_success
# #region test_get_database_mappings_env_not_found [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDatabaseMappingsSuccess
# #region Test.Tests.TestGetDatabaseMappingsEnvNotFound [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate database mapping suggestions return 404 when either environment is missing.
# @PRE source_env_id and target_env_id are valid environment IDs
def test_get_database_mappings_env_not_found(mock_deps):
@@ -401,9 +401,9 @@ def test_get_database_mappings_env_not_found(mock_deps):
"/api/dashboards/db-mappings?source_env_id=ghost&target_env_id=t"
)
assert response.status_code == 404
# #endregion test_get_database_mappings_env_not_found
# #region test_get_dashboard_tasks_history_filters_success [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDatabaseMappingsEnvNotFound
# #region Test.Tests.TestGetDashboardTasksHistoryFiltersSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboard task history returns only related backup and LLM tasks.
# @TEST: GET /api/dashboards/{id}/tasks returns backup and llm tasks for dashboard
def test_get_dashboard_tasks_history_filters_success(mock_deps):
@@ -442,9 +442,9 @@ def test_get_dashboard_tasks_history_filters_success(mock_deps):
"llm_dashboard_validation",
"superset-backup",
}
# #endregion test_get_dashboard_tasks_history_filters_success
# #region test_get_dashboard_thumbnail_success [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardTasksHistoryFiltersSuccess
# #region Test.Tests.TestGetDashboardThumbnailSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Validate dashboard thumbnail endpoint proxies image bytes and content type from Superset.
# @TEST: GET /api/dashboards/{id}/thumbnail proxies image bytes from Superset
def test_get_dashboard_thumbnail_success(mock_deps):
@@ -467,9 +467,9 @@ def test_get_dashboard_thumbnail_success(mock_deps):
assert response.status_code == 200
assert response.content == b"fake-image-bytes"
assert response.headers["content-type"].startswith("image/png")
# #endregion test_get_dashboard_thumbnail_success
# #region _build_profile_preference_stub [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardThumbnailSuccess
# #region Test.Tests.BuildProfilePreferenceStub [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Creates profile preference payload stub for dashboards filter contract tests.
# @PRE username can be empty; enabled indicates profile-default toggle state.
# @POST Returns object compatible with ProfileService.get_my_preference contract.
@@ -483,9 +483,9 @@ def _build_profile_preference_stub(username: str, enabled: bool):
payload = MagicMock()
payload.preference = preference
return payload
# #endregion _build_profile_preference_stub
# #region _matches_actor_case_insensitive [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.BuildProfilePreferenceStub
# #region Test.Tests.MatchesActorCaseInsensitive [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @BRIEF Applies trim + case-insensitive owners OR modified_by matching used by route contract tests.
# @PRE owners can be None or list-like values.
# @POST Returns True when bound username matches any owner or modified_by.
@@ -502,9 +502,9 @@ def _matches_actor_case_insensitive(bound_username, owners, modified_by):
return normalized_bound in owner_tokens or bool(
modified_token and modified_token == normalized_bound
)
# #endregion _matches_actor_case_insensitive
# #region test_get_dashboards_profile_filter_contract_owners_or_modified_by [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.MatchesActorCaseInsensitive
# #region Test.Tests.TestGetDashboardsProfileFilterContractOwnersOrModifiedBy [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: GET /api/dashboards applies profile-default filter with owners OR modified_by trim+case-insensitive semantics.
# @BRIEF Validate profile-default filtering matches owner and modifier aliases using normalized Superset actor values.
# @PRE Current user has enabled profile-default preference and bound username.
@@ -561,9 +561,9 @@ def test_get_dashboards_profile_filter_contract_owners_or_modified_by(mock_deps)
assert payload["effective_profile_filter"]["override_show_all"] is False
assert payload["effective_profile_filter"]["username"] == "john_doe"
assert payload["effective_profile_filter"]["match_logic"] == "owners_or_modified_by"
# #endregion test_get_dashboards_profile_filter_contract_owners_or_modified_by
# #region test_get_dashboards_override_show_all_contract [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsProfileFilterContractOwnersOrModifiedBy
# #region Test.Tests.TestGetDashboardsOverrideShowAllContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: GET /api/dashboards honors override_show_all and disables profile-default filter for current page.
# @BRIEF Validate override_show_all bypasses profile-default filtering without changing dashboard list semantics.
# @PRE Profile-default preference exists but override_show_all=true query is provided.
@@ -614,9 +614,9 @@ def test_get_dashboards_override_show_all_contract(mock_deps):
assert payload["effective_profile_filter"]["username"] is None
assert payload["effective_profile_filter"]["match_logic"] is None
profile_service.matches_dashboard_actor.assert_not_called()
# #endregion test_get_dashboards_override_show_all_contract
# #region test_get_dashboards_profile_filter_no_match_results_contract [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsOverrideShowAllContract
# #region Test.Tests.TestGetDashboardsProfileFilterNoMatchResultsContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: GET /api/dashboards returns empty result set when profile-default filter is active and no dashboard actors match.
# @BRIEF Validate profile-default filtering returns an empty dashboard page when no actor aliases match the bound user.
# @PRE Profile-default preference is enabled with bound username and all dashboards are non-matching.
@@ -669,9 +669,9 @@ def test_get_dashboards_profile_filter_no_match_results_contract(mock_deps):
assert payload["effective_profile_filter"]["override_show_all"] is False
assert payload["effective_profile_filter"]["username"] == "john_doe"
assert payload["effective_profile_filter"]["match_logic"] == "owners_or_modified_by"
# #endregion test_get_dashboards_profile_filter_no_match_results_contract
# #region test_get_dashboards_page_context_other_disables_profile_default [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsProfileFilterNoMatchResultsContract
# #region Test.Tests.TestGetDashboardsPageContextOtherDisablesProfileDefault [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: GET /api/dashboards does not auto-apply profile-default filter outside dashboards_main page context.
# @BRIEF Validate non-dashboard page contexts suppress profile-default filtering and preserve unfiltered results.
# @PRE Profile-default preference exists but page_context=other query is provided.
@@ -722,9 +722,9 @@ def test_get_dashboards_page_context_other_disables_profile_default(mock_deps):
assert payload["effective_profile_filter"]["username"] is None
assert payload["effective_profile_filter"]["match_logic"] is None
profile_service.matches_dashboard_actor.assert_not_called()
# #endregion test_get_dashboards_page_context_other_disables_profile_default
# #region test_get_dashboards_profile_filter_matches_display_alias_without_detail_fanout [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsPageContextOtherDisablesProfileDefault
# #region Test.Tests.TestGetDashboardsProfileFilterMatchesDisplayAliasWithoutDetailFanout [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: GET /api/dashboards resolves Superset display-name alias once and filters without per-dashboard detail calls.
# @BRIEF Validate profile-default filtering reuses resolved Superset display aliases without triggering per-dashboard detail fanout.
# @PRE Profile-default filter is active, bound username is `admin`, dashboard actors contain display labels.
@@ -798,9 +798,9 @@ def test_get_dashboards_profile_filter_matches_display_alias_without_detail_fano
assert payload["effective_profile_filter"]["applied"] is True
lookup_adapter.get_users_page.assert_called_once()
superset_client.get_dashboard.assert_not_called()
# #endregion test_get_dashboards_profile_filter_matches_display_alias_without_detail_fanout
# #region test_get_dashboards_profile_filter_matches_owner_object_payload_contract [TYPE Function]
# @RELATION BINDS_TO -> DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsProfileFilterMatchesDisplayAliasWithoutDetailFanout
# #region Test.Tests.TestGetDashboardsProfileFilterMatchesOwnerObjectPayloadContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.DashboardsApiTests
# @TEST: GET /api/dashboards profile-default filter matches Superset owner object payloads.
# @BRIEF Validate profile-default filtering accepts owner object payloads once aliases resolve to the bound Superset username.
# @PRE Profile-default preference is enabled and owners list contains dict payloads.
@@ -873,5 +873,5 @@ def test_get_dashboards_profile_filter_matches_owner_object_payload_contract(moc
assert payload["total"] == 1
assert {item["id"] for item in payload["dashboards"]} == {701}
assert payload["dashboards"][0]["title"] == "Featured Charts"
# #endregion test_get_dashboards_profile_filter_matches_owner_object_payload_contract
# #endregion DashboardsApiTests
# #endregion Test.Tests.TestGetDashboardsProfileFilterMatchesOwnerObjectPayloadContract
# #endregion Test.Tests.DashboardsApiTests

View File

@@ -1,7 +1,7 @@
# #region DatasetsApiTests [TYPE Module] [C:3] [SEMANTICS datasets, api, tests, pagination, mapping, docs]
# #region Test.Tests.DatasetsApiTests [TYPE Module] [C:3] [SEMANTICS datasets, api, tests, pagination, mapping, docs]
# @BRIEF Unit tests for datasets API endpoints.
# @LAYER API
# @RELATION DEPENDS_ON -> [DatasetsApi]
# @RELATION DEPENDS_ON -> [Api.Datasets.DatasetsApi]
# @INVARIANT Endpoint contracts remain stable for success and validation failure paths.
import pytest
from unittest.mock import AsyncMock, MagicMock
@@ -61,8 +61,8 @@ def mock_deps():
}
app.dependency_overrides.clear()
client = TestClient(app)
# #region test_get_datasets_success [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #region Test.Tests.TestGetDatasetsSuccess [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @BRIEF Validate successful datasets listing contract for an existing environment.
# @TEST: GET /api/datasets returns 200 and valid schema
# @PRE env_id exists
@@ -92,9 +92,9 @@ def test_get_datasets_success(mock_deps):
assert len(data["datasets"]) >= 0
# Validate against Pydantic model
DatasetsResponse(**data)
# #endregion test_get_datasets_success
# #region test_get_datasets_env_not_found [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestGetDatasetsSuccess
# #region Test.Tests.TestGetDatasetsEnvNotFound [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @BRIEF Validate datasets listing returns 404 when the requested environment does not exist.
# @TEST: GET /api/datasets returns 404 if env_id missing
# @PRE env_id does not exist
@@ -104,9 +104,9 @@ def test_get_datasets_env_not_found(mock_deps):
response = client.get("/api/datasets?env_id=nonexistent")
assert response.status_code == 404
assert "Environment not found" in response.json()["detail"]
# #endregion test_get_datasets_env_not_found
# #region test_get_datasets_invalid_pagination [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestGetDatasetsEnvNotFound
# #region Test.Tests.TestGetDatasetsInvalidPagination [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @BRIEF Validate datasets listing rejects invalid pagination parameters with 400 responses.
# @TEST: GET /api/datasets returns 400 for invalid page/page_size
# @PRE page < 1 or page_size > 100
@@ -127,9 +127,9 @@ def test_get_datasets_invalid_pagination(mock_deps):
response = client.get("/api/datasets?env_id=prod&page_size=101")
assert response.status_code == 400
assert "Page size must be between 1 and 100" in response.json()["detail"]
# #endregion test_get_datasets_invalid_pagination
# #region test_map_columns_success [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestGetDatasetsInvalidPagination
# #region Test.Tests.TestMapColumnsSuccess [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @BRIEF Validate map-columns request creates an async mapping task and returns its identifier.
# @TEST: POST /api/datasets/map-columns creates mapping task
# @PRE Valid env_id, dataset_ids, source_type (sqllab)
@@ -152,9 +152,9 @@ def test_map_columns_success(mock_deps):
assert "task_id" in data
# @POST/@SIDE_EFFECT: create_task was called
mock_deps["task"].create_task.assert_called_once()
# #endregion test_map_columns_success
# #region test_map_columns_invalid_source_type [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestMapColumnsSuccess
# #region Test.Tests.TestMapColumnsInvalidSourceType [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @BRIEF Validate map-columns rejects unsupported source types with a 400 contract response.
# @TEST: POST /api/datasets/map-columns returns 400 for invalid source_type
# @PRE source_type is not 'sqllab' or 'xlsx'
@@ -166,9 +166,9 @@ def test_map_columns_invalid_source_type(mock_deps):
)
assert response.status_code == 400
assert "Source type must be 'sqllab' or 'xlsx'" in response.json()["detail"]
# #endregion test_map_columns_invalid_source_type
# #region test_generate_docs_success [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestMapColumnsInvalidSourceType
# #region Test.Tests.TestGenerateDocsSuccess [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @TEST: POST /api/datasets/generate-docs creates doc generation task
# @PRE Valid env_id, dataset_ids, llm_provider
# @BRIEF Validate generate-docs request creates an async documentation task and returns its identifier.
@@ -191,9 +191,9 @@ def test_generate_docs_success(mock_deps):
assert "task_id" in data
# @POST/@SIDE_EFFECT: create_task was called
mock_deps["task"].create_task.assert_called_once()
# #endregion test_generate_docs_success
# #region test_map_columns_empty_ids [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestGenerateDocsSuccess
# #region Test.Tests.TestMapColumnsEmptyIds [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @BRIEF Validate map-columns rejects empty dataset identifier lists.
# @TEST: POST /api/datasets/map-columns returns 400 for empty dataset_ids
# @PRE dataset_ids is empty
@@ -206,9 +206,9 @@ def test_map_columns_empty_ids(mock_deps):
)
assert response.status_code == 400
assert "At least one dataset ID must be provided" in response.json()["detail"]
# #endregion test_map_columns_empty_ids
# #region test_map_columns_missing_database_id [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestMapColumnsEmptyIds
# #region Test.Tests.TestMapColumnsMissingDatabaseId [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @BRIEF Validate map-columns rejects sqllab source without database_id.
# @TEST: POST /api/datasets/map-columns returns 400 for sqllab without database_id
# @POST Returns 400 error
@@ -219,9 +219,9 @@ def test_map_columns_missing_database_id(mock_deps):
)
assert response.status_code == 400
assert "database_id is required" in response.json()["detail"]
# #endregion test_map_columns_missing_database_id
# #region test_generate_docs_empty_ids [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestMapColumnsMissingDatabaseId
# #region Test.Tests.TestGenerateDocsEmptyIds [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @BRIEF Validate generate-docs rejects empty dataset identifier lists.
# @TEST: POST /api/datasets/generate-docs returns 400 for empty dataset_ids
# @PRE dataset_ids is empty
@@ -234,9 +234,9 @@ def test_generate_docs_empty_ids(mock_deps):
)
assert response.status_code == 400
assert "At least one dataset ID must be provided" in response.json()["detail"]
# #endregion test_generate_docs_empty_ids
# #region test_generate_docs_env_not_found [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestGenerateDocsEmptyIds
# #region Test.Tests.TestGenerateDocsEnvNotFound [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @TEST: POST /api/datasets/generate-docs returns 404 for missing env
# @PRE env_id does not exist
# @BRIEF Validate generate-docs returns 404 when the requested environment cannot be resolved.
@@ -250,9 +250,9 @@ def test_generate_docs_env_not_found(mock_deps):
)
assert response.status_code == 404
assert "Environment not found" in response.json()["detail"]
# #endregion test_generate_docs_env_not_found
# #region test_get_datasets_superset_failure [TYPE Function]
# @RELATION BINDS_TO -> [DatasetsApiTests]
# #endregion Test.Tests.TestGenerateDocsEnvNotFound
# #region Test.Tests.TestGetDatasetsSupersetFailure [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.DatasetsApiTests]
# @BRIEF Validate datasets listing surfaces a 503 contract when Superset access fails.
# @TEST_EDGE external_superset_failure -> {status: 503}
# @POST Returns 503 with stable error detail when upstream dataset fetch fails.
@@ -268,5 +268,5 @@ def test_get_datasets_superset_failure(mock_deps):
response = client.get("/api/datasets?env_id=bad_conn")
assert response.status_code == 503
assert "Failed to fetch datasets" in response.json()["detail"]
# #endregion test_get_datasets_superset_failure
# #endregion DatasetsApiTests
# #endregion Test.Tests.TestGetDatasetsSupersetFailure
# #endregion Test.Tests.DatasetsApiTests

View File

@@ -1,4 +1,4 @@
# #region TestGitApi [TYPE Module] [C:3] [SEMANTICS test, git, api, config, repository]
# #region Test.Tests.TestGitApi [TYPE Module] [C:3] [SEMANTICS test, git, api, config, repository]
# @RELATION BINDS_TO -> [EXT:frontend:GitApi]
# @BRIEF API tests for Git configurations and repository operations.
import asyncio
@@ -11,8 +11,8 @@ from src.api.routes import git as git_routes
from src.models.git import GitProvider, GitRepository, GitServerConfig, GitStatus
# #region DbMock [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [TestGitApi]
# #region Test.Tests.DbMock [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF In-memory session double for git route tests with minimal query/filter persistence semantics.
# @INVARIANT Supports only the SQLAlchemy-like operations exercised by this test module.
class DbMock:
@@ -73,9 +73,9 @@ class DbMock:
item.status = GitStatus.CONNECTED
if not hasattr(item, "last_validated"):
item.last_validated = "2026-03-08T00:00:00Z"
# #endregion DbMock
# #region test_get_git_configs_masks_pat [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.DbMock
# #region Test.Tests.TestGetGitConfigsMasksPat [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate listing git configs masks stored PAT values in API-facing responses.
def test_get_git_configs_masks_pat():
"""
@@ -99,9 +99,9 @@ def test_get_git_configs_masks_pat():
assert len(result) == 1
assert result[0].pat == "********"
assert result[0].name == "Test Server"
# #endregion test_get_git_configs_masks_pat
# #region test_create_git_config_persists_config [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.TestGetGitConfigsMasksPat
# #region Test.Tests.TestCreateGitConfigPersistsConfig [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate creating git config persists supplied server attributes in backing session.
def test_create_git_config_persists_config():
"""
@@ -125,12 +125,12 @@ def test_create_git_config_persists_config():
assert (
result.pat == "new-token"
) # Note: route returns unmasked until serialized by FastAPI usually, but in tests schema might catch it or not.
# #endregion test_create_git_config_persists_config
# #endregion Test.Tests.TestCreateGitConfigPersistsConfig
from src.api.routes.git_schemas import GitServerConfigUpdate
# #region test_update_git_config_modifies_record [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #region Test.Tests.TestUpdateGitConfigModifiesRecord [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate updating git config modifies mutable fields while preserving masked PAT semantics.
def test_update_git_config_modifies_record():
"""
@@ -147,7 +147,7 @@ def test_update_git_config_modifies_record():
last_validated="2026-03-08T00:00:00Z",
)
# The monkeypatched query will return existing_config as it's the only one in the list
# #region SingleConfigDbMock [TYPE Class]
# #region Test.Tests.SingleConfigDbMock [TYPE Class]
class SingleConfigDbMock:
def query(self, *args):
return self
@@ -159,7 +159,7 @@ def test_update_git_config_modifies_record():
pass
def refresh(self, config):
pass
# #endregion SingleConfigDbMock
# #endregion Test.Tests.SingleConfigDbMock
db = SingleConfigDbMock()
update_data = GitServerConfigUpdate(name="Updated Server", pat="********")
result = asyncio.run(
@@ -172,9 +172,9 @@ def test_update_git_config_modifies_record():
existing_config.pat == "old-token"
) # Ensure PAT is not overwritten with asterisks
assert result.pat == "********"
# #endregion test_update_git_config_modifies_record
# #region test_update_git_config_raises_404_if_not_found [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.TestUpdateGitConfigModifiesRecord
# #region Test.Tests.TestUpdateGitConfigRaises404IfNotFound [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate updating non-existent git config raises HTTP 404 contract response.
def test_update_git_config_raises_404_if_not_found():
"""
@@ -191,9 +191,9 @@ def test_update_git_config_raises_404_if_not_found():
)
assert exc_info.value.status_code == 404
assert exc_info.value.detail == "Configuration not found"
# #endregion test_update_git_config_raises_404_if_not_found
# #region test_delete_git_config_removes_record [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.TestUpdateGitConfigRaises404IfNotFound
# #region Test.Tests.TestDeleteGitConfigRemovesRecord [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate deleting existing git config removes record and returns success payload.
def test_delete_git_config_removes_record():
"""
@@ -201,7 +201,7 @@ def test_delete_git_config_removes_record():
@POST: The configuration record is removed from the database.
"""
existing_config = GitServerConfig(id="config-1")
# #region SingleConfigDbMock [TYPE Class]
# #region Test.Tests.SingleConfigDbMock [TYPE Class]
class SingleConfigDbMock:
def query(self, *args):
return self
@@ -213,25 +213,25 @@ def test_delete_git_config_removes_record():
self.deleted = config
def commit(self):
pass
# #endregion SingleConfigDbMock
# #endregion Test.Tests.SingleConfigDbMock
db = SingleConfigDbMock()
result = asyncio.run(git_routes.delete_git_config(config_id="config-1", db=db))
assert db.deleted == existing_config
assert result["status"] == "success"
# #endregion test_delete_git_config_removes_record
# #region test_test_git_config_validates_connection_successfully [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.TestDeleteGitConfigRemovesRecord
# #region Test.Tests.TestTestGitConfigValidatesConnectionSuccessfully [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate test-connection endpoint returns success when provider connectivity check passes.
def test_test_git_config_validates_connection_successfully(monkeypatch):
"""
@PRE: `config` contains provider, url, and pat.
@POST: Returns success if the connection is validated via GitService.
"""
# #region MockGitService [TYPE Class]
# #region Test.Tests.MockGitService [TYPE Class]
class MockGitService:
async def test_connection(self, provider, url, pat):
return True
# #endregion MockGitService
# #endregion Test.Tests.MockGitService
monkeypatch.setattr(git_routes, "git_service", MockGitService())
from src.api.routes.git_schemas import GitServerConfigCreate
config = GitServerConfigCreate(
@@ -243,20 +243,20 @@ def test_test_git_config_validates_connection_successfully(monkeypatch):
db = DbMock([])
result = asyncio.run(git_routes.test_git_config(config=config, db=db))
assert result["status"] == "success"
# #endregion test_test_git_config_validates_connection_successfully
# #region test_test_git_config_fails_validation [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.TestTestGitConfigValidatesConnectionSuccessfully
# #region Test.Tests.TestTestGitConfigFailsValidation [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate test-connection endpoint raises HTTP 400 when provider connectivity check fails.
def test_test_git_config_fails_validation(monkeypatch):
"""
@PRE: `config` contains provider, url, and pat BUT connection fails.
@THROW: HTTPException 400
"""
# #region MockGitService [TYPE Class]
# #region Test.Tests.MockGitService [TYPE Class]
class MockGitService:
async def test_connection(self, provider, url, pat):
return False
# #endregion MockGitService
# #endregion Test.Tests.MockGitService
monkeypatch.setattr(git_routes, "git_service", MockGitService())
from src.api.routes.git_schemas import GitServerConfigCreate
config = GitServerConfigCreate(
@@ -270,22 +270,22 @@ def test_test_git_config_fails_validation(monkeypatch):
asyncio.run(git_routes.test_git_config(config=config, db=db))
assert exc_info.value.status_code == 400
assert exc_info.value.detail == "Connection failed"
# #endregion test_test_git_config_fails_validation
# #region test_list_gitea_repositories_returns_payload [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.TestTestGitConfigFailsValidation
# #region Test.Tests.TestListGiteaRepositoriesReturnsPayload [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate gitea repositories endpoint returns normalized list for GITEA provider configs.
def test_list_gitea_repositories_returns_payload(monkeypatch):
"""
@PRE: config_id exists and provider is GITEA.
@POST: Returns repositories visible to PAT user.
"""
# #region MockGitService [TYPE Class]
# #region Test.Tests.MockGitService [TYPE Class]
class MockGitService:
async def list_gitea_repositories(self, url, pat):
return [
{"name": "test-repo", "full_name": "owner/test-repo", "private": True}
]
# #endregion MockGitService
# #endregion Test.Tests.MockGitService
monkeypatch.setattr(git_routes, "git_service", MockGitService())
existing_config = GitServerConfig(
id="config-1",
@@ -301,9 +301,9 @@ def test_list_gitea_repositories_returns_payload(monkeypatch):
assert len(result) == 1
assert result[0].name == "test-repo"
assert result[0].private is True
# #endregion test_list_gitea_repositories_returns_payload
# #region test_list_gitea_repositories_rejects_non_gitea [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.TestListGiteaRepositoriesReturnsPayload
# #region Test.Tests.TestListGiteaRepositoriesRejectsNonGitea [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate gitea repositories endpoint rejects non-GITEA providers with HTTP 400.
def test_list_gitea_repositories_rejects_non_gitea(monkeypatch):
"""
@@ -322,16 +322,16 @@ def test_list_gitea_repositories_rejects_non_gitea(monkeypatch):
asyncio.run(git_routes.list_gitea_repositories(config_id="config-1", db=db))
assert exc_info.value.status_code == 400
assert "GITEA provider only" in exc_info.value.detail
# #endregion test_list_gitea_repositories_rejects_non_gitea
# #region test_create_remote_repository_creates_provider_repo [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.TestListGiteaRepositoriesRejectsNonGitea
# #region Test.Tests.TestCreateRemoteRepositoryCreatesProviderRepo [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate remote repository creation endpoint maps provider response into normalized payload.
def test_create_remote_repository_creates_provider_repo(monkeypatch):
"""
@PRE: config_id exists and PAT has creation permissions.
@POST: Returns normalized remote repository payload.
"""
# #region MockGitService [TYPE Class]
# #region Test.Tests.MockGitService [TYPE Class]
class MockGitService:
async def create_gitlab_repository(
self, server_url, pat, name, private, description, auto_init, default_branch
@@ -342,7 +342,7 @@ def test_create_remote_repository_creates_provider_repo(monkeypatch):
"private": private,
"clone_url": f"{server_url}/user/{name}.git",
}
# #endregion MockGitService
# #endregion Test.Tests.MockGitService
monkeypatch.setattr(git_routes, "git_service", MockGitService())
from src.api.routes.git_schemas import RemoteRepoCreateRequest
existing_config = GitServerConfig(
@@ -362,9 +362,9 @@ def test_create_remote_repository_creates_provider_repo(monkeypatch):
assert result.provider == GitProvider.GITLAB
assert result.name == "new-repo"
assert result.full_name == "user/new-repo"
# #endregion test_create_remote_repository_creates_provider_repo
# #region test_init_repository_initializes_and_saves_binding [TYPE Function]
# @RELATION BINDS_TO -> [TestGitApi]
# #endregion Test.Tests.TestCreateRemoteRepositoryCreatesProviderRepo
# #region Test.Tests.TestInitRepositoryInitializesAndSavesBinding [TYPE Function]
# @RELATION BINDS_TO -> [Test.Tests.TestGitApi]
# @BRIEF Validate repository initialization endpoint creates local repo and persists dashboard binding.
def test_init_repository_initializes_and_saves_binding(monkeypatch):
"""
@@ -372,13 +372,13 @@ def test_init_repository_initializes_and_saves_binding(monkeypatch):
@POST: Repository is initialized on disk and a GitRepository record is saved in DB.
"""
from src.api.routes.git_schemas import RepoInitRequest
# #region MockGitService [TYPE Class]
# #region Test.Tests.MockGitService [TYPE Class]
class MockGitService:
async def init_repo(self, dashboard_id, remote_url, pat, repo_key, default_branch):
self.init_called = True
async def _get_repo_path(self, dashboard_id, repo_key):
return f"/tmp/repos/{repo_key}"
# #endregion MockGitService
# #endregion Test.Tests.MockGitService
async def resolve_dashboard_id(*args, **kwargs):
return 123
async def resolve_repo_key(*args, **kwargs):
@@ -415,7 +415,7 @@ def test_init_repository_initializes_and_saves_binding(monkeypatch):
assert len(db._added) == 1
assert isinstance(db._added[0], GitRepository)
assert db._added[0].dashboard_id == 123
# #endregion test_init_repository_initializes_and_saves_binding
# #endregion Test.Tests.TestInitRepositoryInitializesAndSavesBinding
def test_init_repository_rejects_remote_from_another_server(monkeypatch):
@@ -444,4 +444,4 @@ def test_init_repository_rejects_remote_from_another_server(monkeypatch):
db=DbMock([config]),
)
)
# #endregion TestGitApi
# #endregion Test.Tests.TestGitApi

View File

@@ -1,4 +1,4 @@
# #region TestGitStatusRoute [TYPE Module] [C:3] [SEMANTICS tests, git, api, status, no_repo]
# #region Test.Tests.TestGitStatusRoute [TYPE Module] [C:3] [SEMANTICS tests, git, api, status, no_repo]
# @BRIEF Validate status endpoint behavior for missing and error repository states.
# @LAYER Domain
# @RELATION BINDS_TO -> [EXT:frontend:GitApi]
@@ -17,8 +17,8 @@ async def _resolved_dashboard_id(*_args, **_kwargs):
return 12
# #region test_get_repository_status_returns_no_repo_payload_for_missing_repo [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #region Test.Tests.TestGetRepositoryStatusReturnsNoRepoPayloadForMissingRepo [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure missing local repository is represented as NO_REPO payload instead of an API error.
# @PRE GitService.get_status raises HTTPException(404).
# @POST Route returns a deterministic NO_REPO status payload.
@@ -34,9 +34,9 @@ def test_get_repository_status_returns_no_repo_payload_for_missing_repo(monkeypa
assert response["sync_state"] == "NO_REPO"
assert response["has_repo"] is False
assert response["current_branch"] is None
# #endregion test_get_repository_status_returns_no_repo_payload_for_missing_repo
# #region test_get_repository_status_propagates_non_404_http_exception [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestGetRepositoryStatusReturnsNoRepoPayloadForMissingRepo
# #region Test.Tests.TestGetRepositoryStatusPropagatesNon404HttpException [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure HTTP exceptions other than 404 are not masked.
# @PRE GitService.get_status raises HTTPException with non-404 status.
# @POST Raised exception preserves original status and detail.
@@ -52,9 +52,9 @@ def test_get_repository_status_propagates_non_404_http_exception(monkeypatch):
asyncio.run(git_routes.get_repository_status(34))
assert exc_info.value.status_code == 409
assert exc_info.value.detail == "Conflict"
# #endregion test_get_repository_status_propagates_non_404_http_exception
# #region test_get_repository_diff_propagates_http_exception [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestGetRepositoryStatusPropagatesNon404HttpException
# #region Test.Tests.TestGetRepositoryDiffPropagatesHttpException [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure diff endpoint preserves domain HTTP errors from GitService.
# @PRE GitService.get_diff raises HTTPException.
# @POST Endpoint raises same HTTPException values.
@@ -67,9 +67,9 @@ def test_get_repository_diff_propagates_http_exception(monkeypatch):
asyncio.run(git_routes.get_repository_diff(12))
assert exc_info.value.status_code == 404
assert exc_info.value.detail == "Repository missing"
# #endregion test_get_repository_diff_propagates_http_exception
# #region test_get_history_wraps_unexpected_error_as_500 [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestGetRepositoryDiffPropagatesHttpException
# #region Test.Tests.TestGetHistoryWrapsUnexpectedErrorAs500 [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure non-HTTP exceptions in history endpoint become deterministic 500 errors.
# @PRE GitService.get_commit_history raises ValueError.
# @POST Endpoint returns HTTPException with status 500 and route context.
@@ -82,9 +82,9 @@ def test_get_history_wraps_unexpected_error_as_500(monkeypatch):
asyncio.run(git_routes.get_history(12))
assert exc_info.value.status_code == 500
assert exc_info.value.detail == "get_history failed: broken parser"
# #endregion test_get_history_wraps_unexpected_error_as_500
# #region test_commit_changes_wraps_unexpected_error_as_500 [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestGetHistoryWrapsUnexpectedErrorAs500
# #region Test.Tests.TestCommitChangesWrapsUnexpectedErrorAs500 [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure commit endpoint does not leak unexpected errors as 400.
# @PRE GitService.commit_changes raises RuntimeError.
# @POST Endpoint raises HTTPException(500) with route context.
@@ -100,9 +100,9 @@ def test_commit_changes_wraps_unexpected_error_as_500(monkeypatch):
asyncio.run(git_routes.commit_changes(12, CommitPayload()))
assert exc_info.value.status_code == 500
assert exc_info.value.detail == "commit_changes failed: index lock"
# #endregion test_commit_changes_wraps_unexpected_error_as_500
# #region test_get_repository_status_batch_returns_mixed_statuses [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestCommitChangesWrapsUnexpectedErrorAs500
# #region Test.Tests.TestGetRepositoryStatusBatchReturnsMixedStatuses [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure batch endpoint returns per-dashboard statuses in one response.
# @PRE Some repositories are missing and some are initialized.
# @POST Returned map includes resolved status for each requested dashboard ID.
@@ -121,9 +121,9 @@ def test_get_repository_status_batch_returns_mixed_statuses(monkeypatch):
response = asyncio.run(git_routes.get_repository_status_batch(BatchRequest()))
assert response.statuses["1"]["sync_status"] == "NO_REPO"
assert response.statuses["2"]["sync_state"] == "SYNCED"
# #endregion test_get_repository_status_batch_returns_mixed_statuses
# #region test_get_repository_status_batch_marks_item_as_error_on_service_failure [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestGetRepositoryStatusBatchReturnsMixedStatuses
# #region Test.Tests.TestGetRepositoryStatusBatchMarksItemAsErrorOnServiceFailure [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure batch endpoint marks failed items as ERROR without failing entire request.
# @PRE GitService raises non-HTTP exception for one dashboard.
# @POST Failed dashboard status is marked as ERROR.
@@ -140,9 +140,9 @@ def test_get_repository_status_batch_marks_item_as_error_on_service_failure(monk
response = asyncio.run(git_routes.get_repository_status_batch(BatchRequest()))
assert response.statuses["9"]["sync_status"] == "ERROR"
assert response.statuses["9"]["sync_state"] == "ERROR"
# #endregion test_get_repository_status_batch_marks_item_as_error_on_service_failure
# #region test_get_repository_status_batch_deduplicates_and_truncates_ids [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestGetRepositoryStatusBatchMarksItemAsErrorOnServiceFailure
# #region Test.Tests.TestGetRepositoryStatusBatchDeduplicatesAndTruncatesIds [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure batch endpoint protects server from oversized payloads.
# @PRE request includes duplicate IDs and more than MAX_REPOSITORY_STATUS_BATCH entries.
# @POST Result contains unique IDs up to configured cap.
@@ -159,9 +159,9 @@ def test_get_repository_status_batch_deduplicates_and_truncates_ids(monkeypatch)
response = asyncio.run(git_routes.get_repository_status_batch(BatchRequest()))
assert len(response.statuses) == git_routes.MAX_REPOSITORY_STATUS_BATCH
assert "1" in response.statuses
# #endregion test_get_repository_status_batch_deduplicates_and_truncates_ids
# #region test_commit_changes_applies_profile_identity_before_commit [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestGetRepositoryStatusBatchDeduplicatesAndTruncatesIds
# #region Test.Tests.TestCommitChangesAppliesProfileIdentityBeforeCommit [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure commit route configures repository identity from profile preferences before commit call.
# @PRE Profile preference contains git_username/git_email for current user.
# @POST git_service.configure_identity receives resolved identity and commit proceeds.
@@ -208,9 +208,9 @@ def test_commit_changes_applies_profile_identity_before_commit(monkeypatch):
)
assert identity_service.configured_identity == (12, "user_1", "user1@mail.ru")
assert identity_service.commit_payload == (12, "test", ["dashboards/a.yaml"])
# #endregion test_commit_changes_applies_profile_identity_before_commit
# #region test_pull_changes_applies_profile_identity_before_pull [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestCommitChangesAppliesProfileIdentityBeforeCommit
# #region Test.Tests.TestPullChangesAppliesProfileIdentityBeforePull [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure pull route configures repository identity from profile preferences before pull call.
# @PRE Profile preference contains git_username/git_email for current user.
# @POST git_service.configure_identity receives resolved identity and pull proceeds.
@@ -253,9 +253,9 @@ def test_pull_changes_applies_profile_identity_before_pull(monkeypatch):
)
assert identity_service.configured_identity == (12, "user_1", "user1@mail.ru")
assert identity_service.pulled_payload == (12, None)
# #endregion test_pull_changes_applies_profile_identity_before_pull
# #region test_push_changes_passes_decrypted_profile_pat [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestPullChangesAppliesProfileIdentityBeforePull
# #region Test.Tests.TestPushChangesPassesDecryptedProfilePat [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure push route decrypts current user's profile PAT and passes it to GitService.
# @PRE Profile preference contains encrypted git_personal_access_token.
# @POST git_service.push_changes receives the decrypted token via pat keyword.
@@ -294,9 +294,9 @@ def test_push_changes_passes_decrypted_profile_pat(monkeypatch):
)
)
assert push_service.pushed_payload == (12, "profile-pat")
# #endregion test_push_changes_passes_decrypted_profile_pat
# #region test_pull_changes_passes_decrypted_profile_pat [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestPushChangesPassesDecryptedProfilePat
# #region Test.Tests.TestPullChangesPassesDecryptedProfilePat [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure pull route decrypts current user's profile PAT and passes it to GitService.
# @PRE Profile preference contains git identity and encrypted PAT.
# @POST git_service.pull_changes receives decrypted token after identity configuration.
@@ -341,9 +341,9 @@ def test_pull_changes_passes_decrypted_profile_pat(monkeypatch):
)
assert pull_service.configured_identity == (12, "user_1", "user1@mail.ru")
assert pull_service.pulled_payload == (12, "profile-pat")
# #endregion test_pull_changes_passes_decrypted_profile_pat
# #region test_git_sync_mixin_embeds_and_redacts_pat [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestPullChangesPassesDecryptedProfilePat
# #region Test.Tests.TestGitSyncMixinEmbedsAndRedactsPat [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure PAT embedding URL-encodes secrets and redaction removes them from messages.
# @PRE Origin remote uses an HTTPS URL and PAT contains URL-sensitive characters.
# @POST Origin URL receives encoded PAT; redaction hides raw and URL-shaped tokens.
@@ -372,9 +372,9 @@ def test_git_sync_mixin_embeds_and_redacts_pat():
assert "tok@:/secret" not in redacted
assert "tok%40%3A%2Fsecret" not in redacted
assert "***" in redacted
# #endregion test_git_sync_mixin_embeds_and_redacts_pat
# #region test_get_merge_status_returns_service_payload [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestGitSyncMixinEmbedsAndRedactsPat
# #region Test.Tests.TestGetMergeStatusReturnsServicePayload [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure merge status route returns service payload as-is.
# @PRE git_service.get_merge_status returns unfinished merge payload.
# @POST Route response contains has_unfinished_merge=True.
@@ -400,9 +400,9 @@ def test_get_merge_status_returns_service_payload(monkeypatch):
)
assert response["has_unfinished_merge"] is True
assert response["conflicts_count"] == 2
# #endregion test_get_merge_status_returns_service_payload
# #region test_resolve_merge_conflicts_passes_resolution_items_to_service [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestGetMergeStatusReturnsServicePayload
# #region Test.Tests.TestResolveMergeConflictsPassesResolutionItemsToService [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure merge resolve route forwards parsed resolutions to service.
# @PRE resolve_data has one file strategy.
# @POST Service receives normalized list and route returns resolved files.
@@ -430,9 +430,9 @@ def test_resolve_merge_conflicts_passes_resolution_items_to_service(monkeypatch)
assert captured["dashboard_id"] == 12
assert captured["resolutions"][0]["resolution"] == "mine"
assert response["resolved_files"] == ["dashboards/a.yaml"]
# #endregion test_resolve_merge_conflicts_passes_resolution_items_to_service
# #region test_abort_merge_calls_service_and_returns_result [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestResolveMergeConflictsPassesResolutionItemsToService
# #region Test.Tests.TestAbortMergeCallsServiceAndReturnsResult [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure abort route delegates to service.
# @PRE Service abort_merge returns aborted status.
# @POST Route returns aborted status.
@@ -450,9 +450,9 @@ def test_abort_merge_calls_service_and_returns_result(monkeypatch):
)
)
assert response["status"] == "aborted"
# #endregion test_abort_merge_calls_service_and_returns_result
# #region test_continue_merge_passes_message_and_returns_commit [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# #endregion Test.Tests.TestAbortMergeCallsServiceAndReturnsResult
# #region Test.Tests.TestContinueMergePassesMessageAndReturnsCommit [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestGitStatusRoute
# @BRIEF Ensure continue route passes commit message to service.
# @PRE continue_data.message is provided.
# @POST Route returns committed status and hash.
@@ -475,5 +475,5 @@ def test_continue_merge_passes_message_and_returns_commit(monkeypatch):
)
assert response["status"] == "committed"
assert response["commit_hash"] == "abc123"
# #endregion test_continue_merge_passes_message_and_returns_commit
# #endregion TestGitStatusRoute
# #endregion Test.Tests.TestContinueMergePassesMessageAndReturnsCommit
# #endregion Test.Tests.TestGitStatusRoute

View File

@@ -1,8 +1,8 @@
# #region TestMigrationRoutes [TYPE Module] [C:3] [SEMANTICS test, migration, api, route, handler]
# #region Test.Tests.TestMigrationRoutes [TYPE Module] [C:3] [SEMANTICS test, migration, api, route, handler]
#
# @BRIEF Unit tests for migration API route handlers.
# @LAYER API
# @RELATION BINDS_TO -> [MigrationApi]
# @RELATION BINDS_TO -> [Api.Migration.MigrationApi]
#
from datetime import UTC, datetime
from pathlib import Path
@@ -54,8 +54,8 @@ def db_session():
session = Session()
yield session
session.close()
# #region _make_config_manager [TYPE Function]
# @RELATION BINDS_TO -> TestMigrationRoutes
# #region Test.Tests.MakeConfigManager [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestMigrationRoutes
def _make_config_manager(cron="0 2 * * *"):
"""Creates a mock config manager with a realistic AppConfig-like object."""
settings = MagicMock()
@@ -67,7 +67,7 @@ def _make_config_manager(cron="0 2 * * *"):
cm.save_config = MagicMock()
return cm
# --- get_migration_settings tests ---
# #endregion _make_config_manager
# #endregion Test.Tests.MakeConfigManager
@pytest.mark.asyncio
async def test_get_migration_settings_returns_default_cron():
"""Verify the settings endpoint returns the stored cron string."""
@@ -270,8 +270,8 @@ async def test_get_resource_mappings_filter_by_type(db_session):
assert result["items"][0]["resource_type"] == "dataset"
# --- trigger_sync_now tests ---
@pytest.fixture
# #region _mock_env [TYPE Function]
# @RELATION BINDS_TO -> TestMigrationRoutes
# #region Test.Tests.MockEnv [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestMigrationRoutes
def _mock_env():
"""Creates a mock config environment object."""
env = MagicMock()
@@ -283,9 +283,9 @@ def _mock_env():
env.verify_ssl = False
env.timeout = 30
return env
# #endregion _mock_env
# #region _make_sync_config_manager [TYPE Function]
# @RELATION BINDS_TO -> TestMigrationRoutes
# #endregion Test.Tests.MockEnv
# #region Test.Tests.MakeSyncConfigManager [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestMigrationRoutes
def _make_sync_config_manager(environments):
"""Creates a mock config manager with environments list."""
settings = MagicMock()
@@ -297,7 +297,7 @@ def _make_sync_config_manager(environments):
cm.get_config.return_value = config
cm.get_environments.return_value = environments
return cm
# #endregion _make_sync_config_manager
# #endregion Test.Tests.MakeSyncConfigManager
@pytest.mark.asyncio
async def test_trigger_sync_now_creates_env_row_and_syncs(db_session, _mock_env):
"""Verify that trigger_sync_now creates an Environment row in DB before syncing,
@@ -540,4 +540,4 @@ async def test_dry_run_migration_rejects_same_environment(db_session):
selection=selection, config_manager=cm, db=db_session, _=None
)
assert exc.value.status_code == 400
# #endregion TestMigrationRoutes
# #endregion Test.Tests.TestMigrationRoutes

View File

@@ -1,5 +1,5 @@
# #region TestProfileApi [TYPE Module] [C:3] [SEMANTICS tests, profile, api, preferences, lookup, contract]
# @RELATION BINDS_TO -> SrcRoot
# #region Test.Tests.TestProfileApi [TYPE Module] [C:3] [SEMANTICS tests, profile, api, preferences, lookup, contract]
# @RELATION BINDS_TO -> Init.SrcRoot
# @BRIEF Verifies profile API route contracts for preference read/update and Superset account lookup.
# @LAYER API
# [SECTION: IMPORTS]
@@ -27,8 +27,8 @@ from src.services.profile_service import (
# [/SECTION]
client = TestClient(app)
# #region mock_profile_route_dependencies [TYPE Function]
# @RELATION BINDS_TO -> TestProfileApi
# #region Test.Tests.MockProfileRouteDependencies [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestProfileApi
# @BRIEF Provides deterministic dependency overrides for profile route tests.
# @PRE App instance is initialized.
# @POST Dependencies are overridden for current test and restored afterward.
@@ -42,9 +42,9 @@ def mock_profile_route_dependencies():
app.dependency_overrides[get_db] = lambda: mock_db
app.dependency_overrides[get_config_manager] = lambda: mock_config_manager
return mock_user, mock_db, mock_config_manager
# #endregion mock_profile_route_dependencies
# #region profile_route_deps_fixture [TYPE Function]
# @RELATION BINDS_TO -> TestProfileApi
# #endregion Test.Tests.MockProfileRouteDependencies
# #region Test.Tests.ProfileRouteDepsFixture [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestProfileApi
# @BRIEF Pytest fixture wrapper for profile route dependency overrides.
# @PRE None.
# @POST Yields overridden dependencies and clears overrides after test.
@@ -56,9 +56,9 @@ def profile_route_deps_fixture():
yielded = mock_profile_route_dependencies()
yield yielded
app.dependency_overrides.clear()
# #endregion profile_route_deps_fixture
# #region _build_preference_response [TYPE Function]
# @RELATION BINDS_TO -> TestProfileApi
# #endregion Test.Tests.ProfileRouteDepsFixture
# #region Test.Tests.BuildPreferenceResponse [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestProfileApi
# @BRIEF Builds stable profile preference response payload for route tests.
# @PRE user_id is provided.
# @POST Returns ProfilePreferenceResponse object with deterministic timestamps.
@@ -95,9 +95,9 @@ def _build_preference_response(user_id: str = "u-1") -> ProfilePreferenceRespons
],
),
)
# #endregion _build_preference_response
# #region test_get_profile_preferences_returns_self_payload [TYPE Function]
# @RELATION BINDS_TO -> TestProfileApi
# #endregion Test.Tests.BuildPreferenceResponse
# #region Test.Tests.TestGetProfilePreferencesReturnsSelfPayload [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestProfileApi
# @BRIEF Verifies GET /api/profile/preferences returns stable self-scoped payload.
# @PRE Authenticated user context is available.
# @POST Response status is 200 and payload contains current user preference.
@@ -124,9 +124,9 @@ def test_get_profile_preferences_returns_self_payload(profile_route_deps_fixture
assert payload["security"]["current_role"] == "Data Engineer"
assert payload["security"]["permissions"][0]["key"] == "migration:run"
service.get_my_preference.assert_called_once_with(mock_user)
# #endregion test_get_profile_preferences_returns_self_payload
# #region test_patch_profile_preferences_success [TYPE Function]
# @RELATION BINDS_TO -> TestProfileApi
# #endregion Test.Tests.TestGetProfilePreferencesReturnsSelfPayload
# #region Test.Tests.TestPatchProfilePreferencesSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestProfileApi
# @BRIEF Verifies PATCH /api/profile/preferences persists valid payload through route mapping.
# @PRE Valid request payload and authenticated user.
# @POST Response status is 200 with saved preference payload.
@@ -170,9 +170,9 @@ def test_patch_profile_preferences_success(profile_route_deps_fixture):
assert called_kwargs["payload"].start_page == "reports-logs"
assert called_kwargs["payload"].auto_open_task_drawer is False
assert called_kwargs["payload"].dashboards_table_density == "free"
# #endregion test_patch_profile_preferences_success
# #region test_patch_profile_preferences_validation_error [TYPE Function]
# @RELATION BINDS_TO -> TestProfileApi
# #endregion Test.Tests.TestPatchProfilePreferencesSuccess
# #region Test.Tests.TestPatchProfilePreferencesValidationError [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestProfileApi
# @BRIEF Verifies route maps domain validation failure to HTTP 422 with actionable details.
# @PRE Service raises ProfileValidationError.
# @POST Response status is 422 and includes validation messages.
@@ -193,9 +193,9 @@ def test_patch_profile_preferences_validation_error(profile_route_deps_fixture):
payload = response.json()
assert "detail" in payload
assert "Superset username is required when default filter is enabled." in payload["detail"]
# #endregion test_patch_profile_preferences_validation_error
# #region test_patch_profile_preferences_cross_user_denied [TYPE Function]
# @RELATION BINDS_TO -> TestProfileApi
# #endregion Test.Tests.TestPatchProfilePreferencesValidationError
# #region Test.Tests.TestPatchProfilePreferencesCrossUserDenied [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestProfileApi
# @BRIEF Verifies route maps domain authorization guard failure to HTTP 403.
# @PRE Service raises ProfileAuthorizationError.
# @POST Response status is 403 with denial message.
@@ -215,9 +215,9 @@ def test_patch_profile_preferences_cross_user_denied(profile_route_deps_fixture)
assert response.status_code == 403
payload = response.json()
assert payload["detail"] == "Cross-user preference mutation is forbidden"
# #endregion test_patch_profile_preferences_cross_user_denied
# #region test_lookup_superset_accounts_success [TYPE Function]
# @RELATION BINDS_TO -> TestProfileApi
# #endregion Test.Tests.TestPatchProfilePreferencesCrossUserDenied
# #region Test.Tests.TestLookupSupersetAccountsSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestProfileApi
# @BRIEF Verifies lookup route returns success payload with normalized candidates.
# @PRE Valid environment_id and service success response.
# @POST Response status is 200 and items list is returned.
@@ -248,9 +248,9 @@ def test_lookup_superset_accounts_success(profile_route_deps_fixture):
assert payload["environment_id"] == "dev"
assert payload["total"] == 1
assert payload["items"][0]["username"] == "john_doe"
# #endregion test_lookup_superset_accounts_success
# #region test_lookup_superset_accounts_env_not_found [TYPE Function]
# @RELATION BINDS_TO -> TestProfileApi
# #endregion Test.Tests.TestLookupSupersetAccountsSuccess
# #region Test.Tests.TestLookupSupersetAccountsEnvNotFound [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestProfileApi
# @BRIEF Verifies lookup route maps missing environment to HTTP 404.
# @PRE Service raises EnvironmentNotFoundError.
# @POST Response status is 404 with explicit message.
@@ -264,5 +264,5 @@ def test_lookup_superset_accounts_env_not_found(profile_route_deps_fixture):
assert response.status_code == 404
payload = response.json()
assert payload["detail"] == "Environment 'missing-env' not found"
# #endregion test_lookup_superset_accounts_env_not_found
# #endregion TestProfileApi
# #endregion Test.Tests.TestLookupSupersetAccountsEnvNotFound
# #endregion Test.Tests.TestProfileApi

View File

@@ -1,5 +1,5 @@
# #region TestReportsApi [TYPE Module] [C:3] [SEMANTICS tests, reports, api, contract, pagination, filtering]
# @RELATION BINDS_TO -> SrcRoot
# #region Test.Tests.TestReportsApi [TYPE Module] [C:3] [SEMANTICS tests, reports, api, contract, pagination, filtering]
# @RELATION BINDS_TO -> Init.SrcRoot
# @BRIEF Contract tests for GET /api/reports defaults, pagination, and filtering behavior.
# @LAYER Domain
# @INVARIANT API response contract contains {items,total,page,page_size,has_next,applied_filters}.
@@ -14,8 +14,8 @@ from src.dependencies import get_current_user, get_task_manager
# @DEBT: Divergent _FakeTaskManager definition. Canonical version should be in conftest.py. Authz variant is missing get_all_tasks().
# #region _FakeTaskManager [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [TestReportsApi]
# #region Test.Tests.FakeTaskManager [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestReportsApi]
# @BRIEF Minimal task-manager double exposing only get_all_tasks used by reports route tests.
# @INVARIANT Returns pre-seeded tasks without mutation or side effects.
class _FakeTaskManager:
@@ -23,16 +23,16 @@ class _FakeTaskManager:
self._tasks = tasks
def get_all_tasks(self):
return self._tasks
# #endregion _FakeTaskManager
# #region _admin_user [TYPE Function]
# @RELATION BINDS_TO -> TestReportsApi
# #endregion Test.Tests.FakeTaskManager
# #region Test.Tests.AdminUser [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsApi
# @BRIEF Build deterministic admin principal accepted by reports authorization guard.
def _admin_user():
admin_role = SimpleNamespace(name="Admin", permissions=[])
return SimpleNamespace(username="test-admin", roles=[admin_role])
# #endregion _admin_user
# #region _make_task [TYPE Function]
# @RELATION BINDS_TO -> TestReportsApi
# #endregion Test.Tests.AdminUser
# #region Test.Tests.MakeTask [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsApi
# @BRIEF Build Task fixture with controlled timestamps/status for reports list/detail normalization.
def _make_task(
task_id: str,
@@ -51,9 +51,9 @@ def _make_task(
params={"environment_id": "env-1"},
result=result or {"summary": f"{plugin_id} {status.value.lower()}"},
)
# #endregion _make_task
# #region test_get_reports_default_pagination_contract [TYPE Function]
# @RELATION BINDS_TO -> TestReportsApi
# #endregion Test.Tests.MakeTask
# #region Test.Tests.TestGetReportsDefaultPaginationContract [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsApi
# @BRIEF Validate reports list endpoint default pagination and contract keys for mixed task statuses.
def test_get_reports_default_pagination_contract():
now = datetime.now(UTC)
@@ -98,9 +98,9 @@ def test_get_reports_default_pagination_contract():
assert data["applied_filters"]["sort_order"] == "desc"
finally:
app.dependency_overrides.clear()
# #endregion test_get_reports_default_pagination_contract
# #region test_get_reports_filter_and_pagination [TYPE Function]
# @RELATION BINDS_TO -> TestReportsApi
# #endregion Test.Tests.TestGetReportsDefaultPaginationContract
# #region Test.Tests.TestGetReportsFilterAndPagination [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsApi
# @BRIEF Validate reports list endpoint applies task-type/status filters and pagination boundaries.
def test_get_reports_filter_and_pagination():
now = datetime.now(UTC)
@@ -145,9 +145,9 @@ def test_get_reports_filter_and_pagination():
assert data["items"][0]["status"] == "failed"
finally:
app.dependency_overrides.clear()
# #endregion test_get_reports_filter_and_pagination
# #region test_get_reports_handles_mixed_naive_and_aware_datetimes [TYPE Function]
# @RELATION BINDS_TO -> TestReportsApi
# #endregion Test.Tests.TestGetReportsFilterAndPagination
# #region Test.Tests.TestGetReportsHandlesMixedNaiveAndAwareDatetimes [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsApi
# @BRIEF Validate reports sorting remains stable when task timestamps mix naive and timezone-aware datetimes.
def test_get_reports_handles_mixed_naive_and_aware_datetimes():
naive_now = datetime.now(UTC)
@@ -179,9 +179,9 @@ def test_get_reports_handles_mixed_naive_and_aware_datetimes():
assert len(data["items"]) == 2
finally:
app.dependency_overrides.clear()
# #endregion test_get_reports_handles_mixed_naive_and_aware_datetimes
# #region test_get_reports_invalid_filter_returns_400 [TYPE Function]
# @RELATION BINDS_TO -> TestReportsApi
# #endregion Test.Tests.TestGetReportsHandlesMixedNaiveAndAwareDatetimes
# #region Test.Tests.TestGetReportsInvalidFilterReturns400 [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsApi
# @BRIEF Validate reports list endpoint rejects unsupported task type filters with HTTP 400.
def test_get_reports_invalid_filter_returns_400():
now = datetime.now(UTC)
@@ -204,5 +204,5 @@ def test_get_reports_invalid_filter_returns_400():
assert "detail" in body
finally:
app.dependency_overrides.clear()
# #endregion test_get_reports_invalid_filter_returns_400
# #endregion TestReportsApi
# #endregion Test.Tests.TestGetReportsInvalidFilterReturns400
# #endregion Test.Tests.TestReportsApi

View File

@@ -1,5 +1,5 @@
# #region TestReportsDetailApi [TYPE Module] [C:3] [SEMANTICS tests, reports, api, detail, diagnostics]
# @RELATION BINDS_TO -> SrcRoot
# #region Test.Tests.TestReportsDetailApi [TYPE Module] [C:3] [SEMANTICS tests, reports, api, detail, diagnostics]
# @RELATION BINDS_TO -> Init.SrcRoot
# @BRIEF Contract tests for GET /api/reports/{report_id} detail endpoint behavior.
# @LAYER Domain
# @INVARIANT Detail endpoint tests must keep deterministic assertions for success and not-found contracts.
@@ -14,8 +14,8 @@ from src.dependencies import get_current_user, get_task_manager
# @DEBT: Divergent _FakeTaskManager definition. Canonical version should be in conftest.py. Authz variant is missing get_all_tasks().
# #region _FakeTaskManager [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [TestReportsDetailApi]
# #region Test.Tests.FakeTaskManager [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestReportsDetailApi]
# @BRIEF Minimal task-manager double exposing pre-seeded tasks to detail endpoint under test.
# @INVARIANT get_all_tasks returns exactly seeded tasks list.
class _FakeTaskManager:
@@ -23,16 +23,16 @@ class _FakeTaskManager:
self._tasks = tasks
def get_all_tasks(self):
return self._tasks
# #endregion _FakeTaskManager
# #region _admin_user [TYPE Function]
# @RELATION BINDS_TO -> TestReportsDetailApi
# #endregion Test.Tests.FakeTaskManager
# #region Test.Tests.AdminUser [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsDetailApi
# @BRIEF Provide admin principal fixture accepted by reports detail authorization policy.
def _admin_user():
role = SimpleNamespace(name="Admin", permissions=[])
return SimpleNamespace(username="test-admin", roles=[role])
# #endregion _admin_user
# #region _make_task [TYPE Function]
# @RELATION BINDS_TO -> TestReportsDetailApi
# #endregion Test.Tests.AdminUser
# #region Test.Tests.MakeTask [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsDetailApi
# @BRIEF Build deterministic Task payload for reports detail endpoint contract assertions.
def _make_task(task_id: str, plugin_id: str, status: TaskStatus, result=None):
now = datetime.now(UTC)
@@ -47,9 +47,9 @@ def _make_task(task_id: str, plugin_id: str, status: TaskStatus, result=None):
params={"environment_id": "env-1"},
result=result or {"summary": f"{plugin_id} result"},
)
# #endregion _make_task
# #region test_get_report_detail_success [TYPE Function]
# @RELATION BINDS_TO -> TestReportsDetailApi
# #endregion Test.Tests.MakeTask
# #region Test.Tests.TestGetReportDetailSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsDetailApi
# @BRIEF Validate report detail endpoint returns report body with diagnostics and next actions for existing task.
def test_get_report_detail_success():
task = _make_task(
@@ -76,9 +76,9 @@ def test_get_report_detail_success():
assert "next_actions" in data
finally:
app.dependency_overrides.clear()
# #endregion test_get_report_detail_success
# #region test_get_report_detail_not_found [TYPE Function]
# @RELATION BINDS_TO -> TestReportsDetailApi
# #endregion Test.Tests.TestGetReportDetailSuccess
# #region Test.Tests.TestGetReportDetailNotFound [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsDetailApi
# @BRIEF Validate report detail endpoint returns 404 when requested report identifier is absent.
def test_get_report_detail_not_found():
task = _make_task("detail-2", "superset-backup", TaskStatus.SUCCESS)
@@ -90,5 +90,5 @@ def test_get_report_detail_not_found():
assert response.status_code == 404
finally:
app.dependency_overrides.clear()
# #endregion test_get_report_detail_not_found
# #endregion TestReportsDetailApi
# #endregion Test.Tests.TestGetReportDetailNotFound
# #endregion Test.Tests.TestReportsDetailApi

View File

@@ -1,5 +1,5 @@
# #region TestReportsOpenapiConformance [TYPE Module] [C:3] [SEMANTICS tests, reports, openapi, conformance]
# @RELATION BINDS_TO -> SrcRoot
# #region Test.Tests.TestReportsOpenapiConformance [TYPE Module] [C:3] [SEMANTICS tests, reports, openapi, conformance]
# @RELATION BINDS_TO -> Init.SrcRoot
# @BRIEF Validate implemented reports payload shape against OpenAPI-required top-level contract fields.
# @LAYER Domain
# @INVARIANT List and detail payloads include required contract keys.
@@ -13,8 +13,8 @@ from src.core.task_manager.models import Task, TaskStatus
from src.dependencies import get_current_user, get_task_manager
# #region _FakeTaskManager [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [TestReportsOpenapiConformance]
# #region Test.Tests.FakeTaskManager [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [Test.Tests.TestReportsOpenapiConformance]
# @BRIEF Minimal task-manager fake exposing static task list for OpenAPI conformance checks.
# @INVARIANT get_all_tasks returns seeded tasks unchanged.
class _FakeTaskManager:
@@ -22,16 +22,16 @@ class _FakeTaskManager:
self._tasks = tasks
def get_all_tasks(self):
return self._tasks
# #endregion _FakeTaskManager
# #region _admin_user [TYPE Function]
# @RELATION BINDS_TO -> TestReportsOpenapiConformance
# #endregion Test.Tests.FakeTaskManager
# #region Test.Tests.AdminUser [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsOpenapiConformance
# @BRIEF Provide admin principal fixture required by reports routes in conformance tests.
def _admin_user():
role = SimpleNamespace(name="Admin", permissions=[])
return SimpleNamespace(username="test-admin", roles=[role])
# #endregion _admin_user
# #region _task [TYPE Function]
# @RELATION BINDS_TO -> TestReportsOpenapiConformance
# #endregion Test.Tests.AdminUser
# #region Test.Tests.Task [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsOpenapiConformance
# @BRIEF Construct deterministic task fixture consumed by reports list/detail payload assertions.
def _task(task_id: str, plugin_id: str, status: TaskStatus):
now = datetime.now(UTC)
@@ -44,9 +44,9 @@ def _task(task_id: str, plugin_id: str, status: TaskStatus):
params={"environment_id": "env-1"},
result={"summary": f"{plugin_id} {status.value.lower()}"},
)
# #endregion _task
# #region test_reports_list_openapi_required_keys [TYPE Function]
# @RELATION BINDS_TO -> TestReportsOpenapiConformance
# #endregion Test.Tests.Task
# #region Test.Tests.TestReportsListOpenapiRequiredKeys [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsOpenapiConformance
# @BRIEF Verify reports list endpoint includes all required OpenAPI top-level keys.
def test_reports_list_openapi_required_keys():
tasks = [
@@ -71,9 +71,9 @@ def test_reports_list_openapi_required_keys():
assert required.issubset(body.keys())
finally:
app.dependency_overrides.clear()
# #endregion test_reports_list_openapi_required_keys
# #region test_reports_detail_openapi_required_keys [TYPE Function]
# @RELATION BINDS_TO -> TestReportsOpenapiConformance
# #endregion Test.Tests.TestReportsListOpenapiRequiredKeys
# #region Test.Tests.TestReportsDetailOpenapiRequiredKeys [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestReportsOpenapiConformance
# @BRIEF Verify reports detail endpoint returns payload containing the report object key.
def test_reports_detail_openapi_required_keys():
tasks = [_task("r-3", "llm_dashboard_validation", TaskStatus.SUCCESS)]
@@ -87,5 +87,5 @@ def test_reports_detail_openapi_required_keys():
assert "report" in body
finally:
app.dependency_overrides.clear()
# #endregion test_reports_detail_openapi_required_keys
# #endregion TestReportsOpenapiConformance
# #endregion Test.Tests.TestReportsDetailOpenapiRequiredKeys
# #endregion Test.Tests.TestReportsOpenapiConformance

View File

@@ -1,4 +1,4 @@
# #region test_tasks_crud [TYPE Module] [C:2] [SEMANTICS tests, tasks, crud, api, contract]
# #region Test.Tests.TestTasksCrud [TYPE Module] [C:2] [SEMANTICS tests, tasks, crud, api, contract]
# @RELATION BINDS_TO -> [EXT:TasksRouter]
# @BRIEF Comprehensive integration CRUD tests for the tasks API endpoints.
# @LAYER Domain
@@ -60,8 +60,8 @@ def _make_task(task_id="task-1", plugin_id="superset-backup", status=TaskStatus.
)
# #region test_create_task_success [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestCreateTaskSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF POST /api/tasks creates a task and returns 201 with full payload.
def test_create_task_success(crud_client):
"""POST /api/tasks creates a task and returns 201."""
@@ -81,11 +81,11 @@ def test_create_task_success(crud_client):
assert data["status"] == "PENDING"
tm.create_task.assert_awaited_once_with(plugin_id="superset-backup", params={"env": "test"})
# @TEST_EDGE valid_plugin_with_params
# #endregion test_create_task_success
# #endregion Test.Tests.TestCreateTaskSuccess
# #region test_create_task_deprecated_plugin [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestCreateTaskDeprecatedPlugin [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF POST /api/tasks with deprecated llm_dashboard_validation returns 400.
def test_create_task_deprecated_plugin(crud_client):
"""POST /api/tasks with deprecated llm_dashboard_validation returns 400."""
@@ -99,11 +99,11 @@ def test_create_task_deprecated_plugin(crud_client):
assert response.status_code == 400
assert "deprecated" in response.json()["detail"].lower()
# @TEST_EDGE deprecated_plugin_rejected
# #endregion test_create_task_deprecated_plugin
# #endregion Test.Tests.TestCreateTaskDeprecatedPlugin
# #region test_create_task_empty_plugin_id [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestCreateTaskEmptyPluginId [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF POST /api/tasks with empty plugin_id fails Pydantic response validation.
# @RATIONALE The route passes empty plugin_id through to TaskManager which returns a
# plain AsyncMock (not a Task). FastAPI response_model validation on the
@@ -115,11 +115,11 @@ def test_create_task_empty_plugin_id(crud_client):
with pytest.raises(ResponseValidationError):
tc.post("/api/tasks", json={"plugin_id": "", "params": {}})
# @TEST_EDGE empty_string_plugin_id
# #endregion test_create_task_empty_plugin_id
# #endregion Test.Tests.TestCreateTaskEmptyPluginId
# #region test_create_task_value_error [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestCreateTaskValueError [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF POST /api/tasks handles ValueError from TaskManager as 404.
def test_create_task_value_error(crud_client):
"""POST /api/tasks handles ValueError from TaskManager as 404."""
@@ -134,11 +134,11 @@ def test_create_task_value_error(crud_client):
assert response.status_code == 404
assert response.json()["detail"] == "Plugin not found"
# @TEST_EDGE unknown_plugin_value_error
# #endregion test_create_task_value_error
# #endregion Test.Tests.TestCreateTaskValueError
# #region test_get_task_success [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestGetTaskSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks/{id} returns task data for existing task.
def test_get_task_success(crud_client):
"""GET /api/tasks/{id} returns task data."""
@@ -154,11 +154,11 @@ def test_get_task_success(crud_client):
assert data["status"] == "RUNNING"
tm.get_task.assert_called_with("task-abc")
# @TEST_EDGE existing_task_retrieved
# #endregion test_get_task_success
# #endregion Test.Tests.TestGetTaskSuccess
# #region test_get_task_omits_logs_by_default [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestGetTaskOmitsLogsByDefault [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks/{id} strips logs unless include_logs=true (payload size).
def test_get_task_omits_logs_by_default(crud_client):
"""GET /api/tasks/{id} omits logs by default; include_logs=true returns them."""
@@ -182,11 +182,11 @@ def test_get_task_omits_logs_by_default(crud_client):
assert full.status_code == 200
assert len(full.json()["logs"]) == 2
# @TEST_EDGE logs_stripped_unless_opt_in
# #endregion test_get_task_omits_logs_by_default
# #endregion Test.Tests.TestGetTaskOmitsLogsByDefault
# #region test_get_task_not_found [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestGetTaskNotFound [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks/{id} returns 404 for non-existent task.
def test_get_task_not_found(crud_client):
"""GET /api/tasks/{id} returns 404 for non-existent task."""
@@ -198,11 +198,11 @@ def test_get_task_not_found(crud_client):
assert response.status_code == 404
assert response.json()["detail"] == "Task not found"
# @TEST_EDGE missing_task_404
# #endregion test_get_task_not_found
# #endregion Test.Tests.TestGetTaskNotFound
# #region test_list_tasks_empty [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestListTasksEmpty [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks returns empty list when no tasks exist.
def test_list_tasks_empty(crud_client):
"""GET /api/tasks returns empty list when no tasks."""
@@ -216,11 +216,11 @@ def test_list_tasks_empty(crud_client):
tm.get_tasks.assert_called_once()
# @TEST_EDGE empty_task_list
# @TEST_INVARIANT list_returns_array
# #endregion test_list_tasks_empty
# #endregion Test.Tests.TestListTasksEmpty
# #region test_list_tasks_with_filters [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestListTasksWithFilters [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks with status filter and pagination passes params to TaskManager.
def test_list_tasks_with_filters(crud_client):
"""GET /api/tasks with status filter and pagination."""
@@ -241,11 +241,11 @@ def test_list_tasks_with_filters(crud_client):
assert call_kwargs["offset"] == 0
assert call_kwargs["status"] == "SUCCESS"
# @TEST_EDGE paginated_status_filter
# #endregion test_list_tasks_with_filters
# #endregion Test.Tests.TestListTasksWithFilters
# #region test_list_tasks_with_search [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestListTasksWithSearch [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks with search query passes through to TaskManager.
def test_list_tasks_with_search(crud_client):
"""GET /api/tasks with search query passes through to TaskManager."""
@@ -258,11 +258,11 @@ def test_list_tasks_with_search(crud_client):
tm.get_tasks.assert_called_once()
assert tm.get_tasks.call_args.kwargs["search"] == "backup"
# @TEST_EDGE search_param_propagated
# #endregion test_list_tasks_with_search
# #endregion Test.Tests.TestListTasksWithSearch
# #region test_list_tasks_completed_only [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestListTasksCompletedOnly [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks?completed_only=true passes completed_only flag to TaskManager.
def test_list_tasks_completed_only(crud_client):
"""GET /api/tasks?completed_only=true returns only completed tasks."""
@@ -274,11 +274,11 @@ def test_list_tasks_completed_only(crud_client):
assert response.status_code == 200
assert tm.get_tasks.call_args.kwargs["completed_only"] is True
# @TEST_EDGE completed_only_flag
# #endregion test_list_tasks_completed_only
# #endregion Test.Tests.TestListTasksCompletedOnly
# #region test_list_tasks_invalid_task_type [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestListTasksInvalidTaskType [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks?task_type=invalid returns 400.
def test_list_tasks_invalid_task_type(crud_client):
"""GET /api/tasks?task_type=invalid returns 400."""
@@ -286,11 +286,11 @@ def test_list_tasks_invalid_task_type(crud_client):
response = tc.get("/api/tasks?task_type=unknown_type")
assert response.status_code == 400
# @TEST_EDGE unsupported_task_type
# #endregion test_list_tasks_invalid_task_type
# #endregion Test.Tests.TestListTasksInvalidTaskType
# #region test_list_tasks_task_type_filter [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestListTasksTaskTypeFilter [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks?task_type=backup expands to plugin filter via TASK_TYPE_PLUGIN_MAP.
def test_list_tasks_task_type_filter(crud_client):
"""GET /api/tasks?task_type=backup expands to plugin filter."""
@@ -302,11 +302,11 @@ def test_list_tasks_task_type_filter(crud_client):
assert response.status_code == 200
assert tm.get_tasks.call_args.kwargs["plugin_ids"] == ["superset-backup"]
# @TEST_EDGE task_type_mapped_to_plugin
# #endregion test_list_tasks_task_type_filter
# #endregion Test.Tests.TestListTasksTaskTypeFilter
# #region test_list_tasks_plugin_id_filter [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestListTasksPluginIdFilter [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF GET /api/tasks?plugin_id=X filters by plugin_id passed as list to TaskManager.
def test_list_tasks_plugin_id_filter(crud_client):
"""GET /api/tasks?plugin_id=X filters by plugin."""
@@ -318,11 +318,11 @@ def test_list_tasks_plugin_id_filter(crud_client):
assert response.status_code == 200
assert tm.get_tasks.call_args.kwargs["plugin_ids"] == ["superset-backup"]
# @TEST_EDGE plugin_id_param_as_list
# #endregion test_list_tasks_plugin_id_filter
# #endregion Test.Tests.TestListTasksPluginIdFilter
# #region test_clear_tasks_by_status [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestClearTasksByStatus [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF DELETE /api/tasks?status=SUCCESS clears completed tasks and returns 204.
def test_clear_tasks_by_status(crud_client):
"""DELETE /api/tasks?status=SUCCESS clears completed tasks."""
@@ -335,11 +335,11 @@ def test_clear_tasks_by_status(crud_client):
tm.clear_tasks.assert_called_once()
assert tm.clear_tasks.call_args.args[0] == "SUCCESS"
# @TEST_EDGE clear_with_status_filter
# #endregion test_clear_tasks_by_status
# #endregion Test.Tests.TestClearTasksByStatus
# #region test_clear_tasks_no_status [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestClearTasksNoStatus [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF DELETE /api/tasks without status clears all tasks and returns 204.
def test_clear_tasks_no_status(crud_client):
"""DELETE /api/tasks without status clears all."""
@@ -352,11 +352,11 @@ def test_clear_tasks_no_status(crud_client):
tm.clear_tasks.assert_called_once()
assert tm.clear_tasks.call_args.args[0] is None
# @TEST_EDGE clear_all_no_filter
# #endregion test_clear_tasks_no_status
# #endregion Test.Tests.TestClearTasksNoStatus
# #region test_full_crud_cycle [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_crud
# #region Test.Tests.TestFullCrudCycle [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksCrud
# @BRIEF Full Create -> Get -> List -> Delete cycle for a task verifies end-to-end lifecycle.
def test_full_crud_cycle(crud_client):
"""Full Create -> Get -> List -> Delete cycle for a task."""
@@ -395,6 +395,6 @@ def test_full_crud_cycle(crud_client):
response = tc.get("/api/tasks/full-cycle-1")
assert response.status_code == 404
# @TEST_EDGE create_get_list_delete_verify_gone
# #endregion test_full_crud_cycle
# #endregion Test.Tests.TestFullCrudCycle
# #endregion test_tasks_crud
# #endregion Test.Tests.TestTasksCrud

View File

@@ -1,4 +1,4 @@
# #region test_tasks_logs_module [TYPE Module] [C:2] [SEMANTICS tests, tasks, logs, api, contract, validation]
# #region Test.Tests.TestTasksLogsModule [TYPE Module] [C:2] [SEMANTICS tests, tasks, logs, api, contract, validation]
# @RELATION BINDS_TO -> [EXT:frontend:TasksModule]
# @BRIEF Contract testing for task logs API endpoints.
# @LAYER Domain
@@ -26,8 +26,8 @@ def client():
return TestClient(app), mock_tm
# @TEST_CONTRACT get_task_logs_api -> Invariants
# @TEST_FIXTURE valid_task_logs_request
# #region test_get_task_logs_success [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_logs_module
# #region Test.Tests.TestGetTaskLogsSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksLogsModule
# @BRIEF Validate task logs endpoint returns filtered logs for an existing task.
def test_get_task_logs_success(client):
tc, tm = client
@@ -44,9 +44,9 @@ def test_get_task_logs_success(client):
assert args[0][0] == "task-1"
assert args[0][1].level == "INFO"
# @TEST_EDGE task_not_found
# #endregion test_get_task_logs_success
# #region test_get_task_logs_not_found [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_logs_module
# #endregion Test.Tests.TestGetTaskLogsSuccess
# #region Test.Tests.TestGetTaskLogsNotFound [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksLogsModule
# @BRIEF Validate task logs endpoint returns 404 when the task identifier is missing.
def test_get_task_logs_not_found(client):
tc, tm = client
@@ -55,9 +55,9 @@ def test_get_task_logs_not_found(client):
assert response.status_code == 404
assert response.json()["detail"] == "Task not found"
# @TEST_EDGE invalid_limit
# #endregion test_get_task_logs_not_found
# #region test_get_task_logs_invalid_limit [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_logs_module
# #endregion Test.Tests.TestGetTaskLogsNotFound
# #region Test.Tests.TestGetTaskLogsInvalidLimit [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksLogsModule
# @BRIEF Validate task logs endpoint enforces query validation for limit lower bound.
def test_get_task_logs_invalid_limit(client):
tc, tm = client
@@ -65,9 +65,9 @@ def test_get_task_logs_invalid_limit(client):
response = tc.get("/tasks/task-1/logs?limit=0")
assert response.status_code == 422
# @TEST_INVARIANT response_purity
# #endregion test_get_task_logs_invalid_limit
# #region test_get_task_log_stats_success [TYPE Function]
# @RELATION BINDS_TO -> test_tasks_logs_module
# #endregion Test.Tests.TestGetTaskLogsInvalidLimit
# #region Test.Tests.TestGetTaskLogStatsSuccess [TYPE Function]
# @RELATION BINDS_TO -> Test.Tests.TestTasksLogsModule
# @BRIEF Validate log stats endpoint returns success payload for an existing task.
def test_get_task_log_stats_success(client):
tc, tm = client
@@ -77,5 +77,5 @@ def test_get_task_log_stats_success(client):
assert response.status_code == 200
# response_model=LogStats might wrap this, but let's check basic structure
# assuming tm.get_task_log_stats returns something compatible with LogStats
# #endregion test_get_task_log_stats_success
# #endregion test_tasks_logs_module
# #endregion Test.Tests.TestGetTaskLogStatsSuccess
# #endregion Test.Tests.TestTasksLogsModule

View File

@@ -1,11 +1,11 @@
# #region AdminApi [C:5] [TYPE Module] [SEMANTICS fastapi, admin, api, rbac, user]
# #region Api.Admin.AdminApi [C:5] [TYPE Module] [SEMANTICS fastapi, admin, api, rbac, user]
# @defgroup Api Module group.
#
# @BRIEF Admin API endpoints for user and role management.
# @LAYER API
# @RELATION DEPENDS_ON -> [AuthRepository]
# @RELATION DEPENDS_ON -> [get_auth_db]
# @RELATION DEPENDS_ON -> [has_permission]
# @RELATION DEPENDS_ON -> [Core.Repository.AuthRepository]
# @RELATION DEPENDS_ON -> [Core.Database.GetAuthDb]
# @RELATION DEPENDS_ON -> [Dependencies.AppDependencies.HasPermission]
#
# @INVARIANT All endpoints in this module require 'Admin' role or 'admin' scope.
# @RATIONALE Centralizes user and role management endpoints behind a permission-gated router because RBAC administration requires consistent enforcement of the 'Admin' role across all user/role/mapping operations, preventing privilege escalation through inconsistent authorization checks.
@@ -36,15 +36,15 @@ from ...services.rbac_permission_catalog import (
sync_permission_catalog,
)
# #region router [TYPE Variable]
# #region Api.Admin.Router [TYPE Variable]
# @ingroup Api
# @RELATION DEPENDS_ON -> fastapi.APIRouter
# @BRIEF APIRouter instance for admin routes.
router = APIRouter(prefix="/api/admin", tags=["admin"])
# #endregion router
# #endregion Api.Admin.Router
# #region list_users [C:3] [TYPE Function]
# #region Api.Admin.ListUsers [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Lists all registered users.
# @PRE Current user has 'Admin' role.
@@ -59,15 +59,15 @@ async def list_users(
return users
# #endregion list_users
# #endregion Api.Admin.ListUsers
# #region create_user [C:3] [TYPE Function]
# #region Api.Admin.CreateUser [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Creates a new local user.
# @PRE Current user has 'Admin' role.
# @POST New user is created in the database.
# @RELATION CALLS -> [AuthRepository]
# @RELATION CALLS -> [Core.Repository.AuthRepository]
@router.post("/users", response_model=UserSchema, status_code=status.HTTP_201_CREATED)
async def create_user(
user_in: UserCreate,
@@ -98,15 +98,15 @@ async def create_user(
return new_user
# #endregion create_user
# #endregion Api.Admin.CreateUser
# #region update_user [C:3] [TYPE Function]
# #region Api.Admin.UpdateUser [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Updates an existing user.
# @PRE Current user has 'Admin' role.
# @POST User record is updated in the database.
# @RELATION CALLS -> AuthRepository
# @RELATION CALLS -> Core.Repository.AuthRepository
@router.put("/users/{user_id}", response_model=UserSchema)
async def update_user(
user_id: str,
@@ -139,15 +139,15 @@ async def update_user(
return user
# #endregion update_user
# #endregion Api.Admin.UpdateUser
# #region delete_user [C:3] [TYPE Function]
# #region Api.Admin.DeleteUser [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Deletes a user.
# @PRE Current user has 'Admin' role.
# @POST User record is removed from the database.
# @RELATION CALLS -> AuthRepository
# @RELATION CALLS -> Core.Repository.AuthRepository
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(
user_id: str,
@@ -169,13 +169,13 @@ async def delete_user(
return None
# #endregion delete_user
# #endregion Api.Admin.DeleteUser
# #region list_roles [C:3] [TYPE Function]
# #region Api.Admin.ListRoles [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Lists all available roles.
# @RELATION CALLS -> [Role]
# @RELATION CALLS -> [Models.Auth.Role]
@router.get("/roles", response_model=list[RoleSchema])
async def list_roles(
db: Session = Depends(get_auth_db), _=Depends(has_permission("admin:roles", "READ"))
@@ -184,16 +184,16 @@ async def list_roles(
return db.query(Role).all()
# #endregion list_roles
# #endregion Api.Admin.ListRoles
# #region create_role [C:3] [TYPE Function]
# #region Api.Admin.CreateRole [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Creates a new system role with associated permissions.
# @PRE Role name must be unique.
# @POST New Role record is created in auth.db.
# @SIDE_EFFECT Commits new role and associations to auth.db.
# @RELATION CALLS -> [get_permission_by_id]
# @RELATION CALLS -> [Core.Repository.GetPermissionById]
@router.post("/roles", response_model=RoleSchema, status_code=status.HTTP_201_CREATED)
async def create_role(
role_in: RoleCreate,
@@ -222,16 +222,16 @@ async def create_role(
return new_role
# #endregion create_role
# #endregion Api.Admin.CreateRole
# #region update_role [C:3] [TYPE Function]
# #region Api.Admin.UpdateRole [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Updates an existing role's metadata and permissions.
# @PRE role_id must be a valid existing role UUID.
# @POST Role record is updated in auth.db.
# @SIDE_EFFECT Commits updates to auth.db.
# @RELATION CALLS -> [get_role_by_id]
# @RELATION CALLS -> [Core.Repository.GetRoleById]
@router.put("/roles/{role_id}", response_model=RoleSchema)
async def update_role(
role_id: str,
@@ -266,16 +266,16 @@ async def update_role(
return role
# #endregion update_role
# #endregion Api.Admin.UpdateRole
# #region delete_role [C:3] [TYPE Function]
# #region Api.Admin.DeleteRole [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Removes a role from the system.
# @PRE role_id must be a valid existing role UUID.
# @POST Role record is removed from auth.db.
# @SIDE_EFFECT Deletes record from auth.db and commits.
# @RELATION CALLS -> [get_role_by_id]
# @RELATION CALLS -> [Core.Repository.GetRoleById]
@router.delete("/roles/{role_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_role(
role_id: str,
@@ -293,10 +293,10 @@ async def delete_role(
return None
# #endregion delete_role
# #endregion Api.Admin.DeleteRole
# #region list_permissions [C:3] [TYPE Function]
# #region Api.Admin.ListPermissions [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Lists all available system permissions for assignment.
# @POST Returns a list of all PermissionSchema objects.
@@ -324,13 +324,13 @@ async def list_permissions(
return repo.list_permissions()
# #endregion list_permissions
# #endregion Api.Admin.ListPermissions
# #region list_ad_mappings [C:3] [TYPE Function]
# #region Api.Admin.ListAdMappings [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Lists all AD Group to Role mappings.
# @RELATION CALLS -> ADGroupMapping
# @RELATION CALLS -> Models.Auth.ADGroupMapping
@router.get("/ad-mappings", response_model=list[ADGroupMappingSchema])
async def list_ad_mappings(
db: Session = Depends(get_auth_db),
@@ -340,14 +340,14 @@ async def list_ad_mappings(
return db.query(ADGroupMapping).all()
# #endregion list_ad_mappings
# #endregion Api.Admin.ListAdMappings
# #region create_ad_mapping [C:2] [TYPE Function]
# #region Api.Admin.CreateAdMapping [C:2] [TYPE Function]
# @ingroup Api
# @RELATION DEPENDS_ON -> [ADGroupMapping]
# @RELATION DEPENDS_ON -> [get_auth_db]
# @RELATION DEPENDS_ON -> [has_permission]
# @RELATION DEPENDS_ON -> [Models.Auth.ADGroupMapping]
# @RELATION DEPENDS_ON -> [Core.Database.GetAuthDb]
# @RELATION DEPENDS_ON -> [Dependencies.AppDependencies.HasPermission]
# @BRIEF Creates a new AD Group mapping.
@router.post("/ad-mappings", response_model=ADGroupMappingSchema)
async def create_ad_mapping(
@@ -365,6 +365,6 @@ async def create_ad_mapping(
return new_mapping
# #endregion create_ad_mapping
# #endregion Api.Admin.CreateAdMapping
# #endregion AdminApi
# #endregion Api.Admin.AdminApi

View File

@@ -1,9 +1,9 @@
# #region AdminApiKeyRoutes [C:3] [TYPE Module] [SEMANTICS fastapi, admin, api_key, crud]
# #region Api.AdminApiKeys.AdminApiKeyRoutes [C:3] [TYPE Module] [SEMANTICS fastapi, admin, api_key, crud]
# @defgroup Api Module group.
# @BRIEF Admin API endpoints for API key management — list, generate (one-time reveal), and revoke.
# @LAYER API
# @RELATION DEPENDS_ON -> [APIKeyModel]
# @RELATION DEPENDS_ON -> [APIKeyUtilities]
# @RELATION DEPENDS_ON -> [Models.ApiKey.APIKeyModel]
# @RELATION DEPENDS_ON -> [Core.ApiKey.APIKeyUtilities]
# @RELATION DEPENDS_ON -> [EXT:code:has_permission_admin_settings_WRITE]
# @INVARIANT GET /api/admin/api-keys NEVER returns key_hash or raw_key.
# @INVARIANT POST /api/admin/api-keys returns raw_key ONCE — never stored, never retrievable again.
@@ -20,25 +20,25 @@ from ...core.database import get_db
from ...dependencies import has_permission
from ...models.api_key import APIKey
# #region router [TYPE Variable]
# #region Api.AdminApiKeys.Router [TYPE Variable]
# @ingroup Api
# @BRIEF APIRouter for admin API key management routes.
router = APIRouter(prefix="/api/admin/api-keys", tags=["admin", "api-keys"])
# #endregion router
# #endregion Api.AdminApiKeys.Router
# ── Pydantic schemas ──────────────────────────────────────────
# #region ApiKeyCreateRequest [C:1] [TYPE Class]
# #region Api.AdminApiKeys.ApiKeyCreateRequest [C:1] [TYPE Class]
class ApiKeyCreateRequest(BaseModel):
name: str = Field(..., min_length=1, max_length=255)
environment_id: str | None = None
permissions: list[str] = Field(..., min_length=1)
expires_at: datetime | None = None
# #endregion ApiKeyCreateRequest
# #endregion Api.AdminApiKeys.ApiKeyCreateRequest
# #region ApiKeyCreateResponse [C:1] [TYPE Class]
# #region Api.AdminApiKeys.ApiKeyCreateResponse [C:1] [TYPE Class]
class ApiKeyCreateResponse(BaseModel):
id: str
raw_key: str
@@ -49,10 +49,10 @@ class ApiKeyCreateResponse(BaseModel):
active: bool
created_at: datetime
expires_at: datetime | None
# #endregion ApiKeyCreateResponse
# #endregion Api.AdminApiKeys.ApiKeyCreateResponse
# #region ApiKeyListItem [C:1] [TYPE Class]
# #region Api.AdminApiKeys.ApiKeyListItem [C:1] [TYPE Class]
class ApiKeyListItem(BaseModel):
id: str
name: str
@@ -65,19 +65,19 @@ class ApiKeyListItem(BaseModel):
last_used_at: datetime | None
model_config = ConfigDict(from_attributes=True)
# #endregion ApiKeyListItem
# #endregion Api.AdminApiKeys.ApiKeyListItem
# #region ApiKeyRevokeResponse [C:1] [TYPE Class]
# #region Api.AdminApiKeys.ApiKeyRevokeResponse [C:1] [TYPE Class]
class ApiKeyRevokeResponse(BaseModel):
id: str
status: str
# #endregion ApiKeyRevokeResponse
# #endregion Api.AdminApiKeys.ApiKeyRevokeResponse
# ── Routes ────────────────────────────────────────────────────
# #region list_api_keys [C:2] [TYPE Function]
# #region Api.AdminApiKeys.ListApiKeys [C:2] [TYPE Function]
# @ingroup Api
# @BRIEF List all API keys — NEVER returns key_hash or raw_key.
# @PRE Requires admin:settings WRITE permission.
@@ -102,16 +102,16 @@ async def list_api_keys(
)
for k in keys
]
# #endregion list_api_keys
# #endregion Api.AdminApiKeys.ListApiKeys
# #region create_api_key [C:3] [TYPE Function]
# #region Api.AdminApiKeys.CreateApiKey [C:3] [TYPE Function]
# @ingroup Api
# @BRIEF Generate a new API key — returns raw key ONCE, never stored or retrievable again.
# @PRE Requires admin:settings WRITE permission. name is required, at least one permission.
# @POST Creates APIKey row with SHA-256 hash. Returns raw key in response.
# @SIDE_EFFECT Generates cryptographically random key, stores hash in DB.
# @RELATION DEPENDS_ON -> [generate_api_key]
# @RELATION DEPENDS_ON -> [Core.ApiKey.GenerateApiKey]
@router.post("/", response_model=ApiKeyCreateResponse, status_code=status.HTTP_201_CREATED)
async def create_api_key(
request: ApiKeyCreateRequest,
@@ -152,10 +152,10 @@ async def create_api_key(
created_at=api_key.created_at,
expires_at=api_key.expires_at,
)
# #endregion create_api_key
# #endregion Api.AdminApiKeys.CreateApiKey
# #region revoke_api_key [C:2] [TYPE Function]
# #region Api.AdminApiKeys.RevokeApiKey [C:2] [TYPE Function]
# @ingroup Api
# @BRIEF Revoke an API key by setting active=False. Preserves row for audit.
# @PRE Requires admin:settings WRITE permission.
@@ -185,6 +185,6 @@ async def revoke_api_key(
id=api_key.id,
status="revoked",
)
# #endregion revoke_api_key
# #endregion Api.AdminApiKeys.RevokeApiKey
# #endregion AdminApiKeyRoutes
# #endregion Api.AdminApiKeys.AdminApiKeyRoutes

View File

@@ -2,7 +2,7 @@
# #region Api.AgentLifecycle [C:3] [TYPE Module] [SEMANTICS agent,lifecycle,api,rest]
# @defgroup AgentLifecycle REST routes for agent lifecycle audit events.
# @BRIEF Write (immutable audit) and read (paginated, user-scoped) lifecycle event endpoints.
# @RELATION DEPENDS_ON -> [AgentLifecycleService]
# @RELATION DEPENDS_ON -> [Services.AgentLifecycleService]
# @RELATION DEPENDS_ON -> [AuthMiddleware]
# @INVARIANT POST /api/agent/events validates and reduces payload to safe whitelist before storage.
# @INVARIANT GET /api/agent/events enforces user-scoped access — non-admin users see only own events.

View File

@@ -1,8 +1,8 @@
# #region AgentSupersetRoutes [C:4] [TYPE Module] [SEMANTICS api,agent,superset,sql,dashboard,dataset,crud]
# #region Api.AgentSuperset.AgentSupersetRoutes [C:4] [TYPE Module] [SEMANTICS api,agent,superset,sql,dashboard,dataset,crud]
# @defgroup Api Agent Superset proxy routes for the Gradio agent (write/mutate endpoints).
# @BRIEF FastAPI endpoints proxying SupersetClient write/mutate operations for the agent chat.
# @LAYER API
# @RELATION DEPENDS_ON -> [SupersetClient]
# @RELATION DEPENDS_ON -> [Core.Init.SupersetClient]
# @SIDE_EFFECT Асинхронные HTTP-вызовы к Superset API через SupersetClient.
# @RATIONALE The Gradio agent container has no direct SupersetClient — it reaches Superset
# through these proxy endpoints. Each endpoint resolves the target environment and delegates
@@ -115,7 +115,7 @@ async def agent_sqllab_estimate(
# @ingroup Api
# @BRIEF Create a new dashboard in Superset.
# @SIDE_EFFECT HTTP POST to Superset /api/v1/dashboard/.
# @RELATION CALLS -> [create_dashboard]
# @RELATION CALLS -> [Core.DashboardsWrite.CreateDashboard]
@router.post("/dashboards")
async def agent_dashboard_create(
environment_id: str = Query(...),
@@ -146,7 +146,7 @@ async def agent_dashboard_create(
# @ingroup Api
# @BRIEF Deep-copy a dashboard including all charts.
# @SIDE_EFFECT HTTP POST to Superset /api/v1/dashboard/{id}/copy/.
# @RELATION CALLS -> [copy_dashboard]
# @RELATION CALLS -> [Core.DashboardsWrite.CopyDashboard]
@router.post("/dashboards/{dashboard_id}/copy")
async def agent_dashboard_copy(
dashboard_id: int,
@@ -166,7 +166,7 @@ async def agent_dashboard_copy(
# @ingroup Api
# @BRIEF Update an existing dashboard's properties.
# @SIDE_EFFECT HTTP PUT to Superset /api/v1/dashboard/{id}.
# @RELATION CALLS -> [update_dashboard]
# @RELATION CALLS -> [Core.DashboardsWrite.UpdateDashboard]
@router.put("/dashboards/{dashboard_id}")
async def agent_dashboard_update(
dashboard_id: int,
@@ -201,7 +201,7 @@ async def agent_dashboard_update(
# @ingroup Api
# @BRIEF Create a new dataset in Superset.
# @SIDE_EFFECT HTTP POST to Superset /api/v1/dataset/.
# @RELATION CALLS -> [SupersetClientCreateDataset]
# @RELATION CALLS -> [Core.Datasets.SupersetClientCreateDataset]
@router.post("/datasets")
async def agent_dataset_create(
environment_id: str = Query(...),
@@ -228,7 +228,7 @@ async def agent_dataset_create(
# @ingroup Api
# @BRIEF Delete a dataset from Superset by ID.
# @SIDE_EFFECT HTTP DELETE to Superset /api/v1/dataset/{id}.
# @RELATION CALLS -> [SupersetClientDeleteDataset]
# @RELATION CALLS -> [Core.Datasets.SupersetClientDeleteDataset]
@router.delete("/datasets/{dataset_id}")
async def agent_dataset_delete(
dataset_id: int,
@@ -247,7 +247,7 @@ async def agent_dataset_delete(
# @ingroup Api
# @BRIEF Duplicate a dataset including columns and metrics.
# @SIDE_EFFECT HTTP POST to Superset /api/v1/dataset/duplicate.
# @RELATION CALLS -> [SupersetClientDuplicateDataset]
# @RELATION CALLS -> [Core.Datasets.SupersetClientDuplicateDataset]
@router.post("/datasets/{dataset_id}/duplicate")
async def agent_dataset_duplicate(
dataset_id: int,
@@ -267,7 +267,7 @@ async def agent_dataset_duplicate(
# @ingroup Api
# @BRIEF Rescan columns and types for a dataset from its source database.
# @SIDE_EFFECT HTTP PUT to Superset /api/v1/dataset/{id}/refresh.
# @RELATION CALLS -> [SupersetClientRefreshDatasetSchema]
# @RELATION CALLS -> [Core.Datasets.SupersetClientRefreshDatasetSchema]
@router.post("/datasets/{dataset_id}/refresh")
async def agent_dataset_refresh(
dataset_id: int,
@@ -281,4 +281,4 @@ async def agent_dataset_refresh(
await client.aclose()
# #endregion AgentSuperset.DatasetRefresh
# #endregion AgentSupersetRoutes
# #endregion Api.AgentSuperset.AgentSupersetRoutes

View File

@@ -1,8 +1,8 @@
# #region AgentSupersetExploreRoutes [C:3] [TYPE Module] [SEMANTICS api,agent,superset,database,explore,audit]
# #region Api.AgentSupersetExplore.AgentSupersetExploreRoutes [C:3] [TYPE Module] [SEMANTICS api,agent,superset,database,explore,audit]
# @defgroup Api Agent Superset read-only proxy routes (database exploration, audit, saved queries).
# @BRIEF FastAPI endpoints proxying SupersetClient read-only operations for the agent chat.
# @LAYER API
# @RELATION DEPENDS_ON -> [SupersetClient]
# @RELATION DEPENDS_ON -> [Core.Init.SupersetClient]
# @SIDE_EFFECT Асинхронные HTTP-вызовы к Superset API через SupersetClient.
# @RATIONALE Split from agent_superset.py to satisfy INV_7 (module < 400 lines).
# Write/mutate endpoints remain in agent_superset.py.
@@ -35,7 +35,7 @@ async def _get_superset_client(environment_id: str) -> SupersetClient:
# @ingroup Api
# @BRIEF List all databases available in the environment.
# @SIDE_EFFECT HTTP GET to Superset /api/v1/database/.
# @RELATION CALLS -> [SupersetClientGetDatabasesSummary]
# @RELATION CALLS -> [Core.Databases.SupersetClientGetDatabasesSummary]
@router.get("/databases")
async def agent_list_databases(
environment_id: str = Query(...),
@@ -53,7 +53,7 @@ async def agent_list_databases(
# @ingroup Api
# @BRIEF List all schemas for a database.
# @SIDE_EFFECT HTTP GET to Superset /api/v1/database/{id}/schemas/.
# @RELATION CALLS -> [SupersetClientGetDatabaseSchemas]
# @RELATION CALLS -> [Core.Databases.SupersetClientGetDatabaseSchemas]
@router.get("/databases/{database_id}/schemas")
async def agent_database_schemas(
database_id: int,
@@ -72,7 +72,7 @@ async def agent_database_schemas(
# @ingroup Api
# @BRIEF List tables/views for a database schema.
# @SIDE_EFFECT HTTP GET to Superset /api/v1/database/{id}/tables/.
# @RELATION CALLS -> [SupersetClientGetDatabaseTables]
# @RELATION CALLS -> [Core.Databases.SupersetClientGetDatabaseTables]
@router.get("/databases/{database_id}/tables")
async def agent_database_tables(
database_id: int,
@@ -92,7 +92,7 @@ async def agent_database_tables(
# @ingroup Api
# @BRIEF Get table metadata: columns, types, indexes, primary keys.
# @SIDE_EFFECT HTTP GET to Superset /api/v1/database/{id}/table_metadata/.
# @RELATION CALLS -> [SupersetClientGetTableMetadata]
# @RELATION CALLS -> [Core.Databases.SupersetClientGetTableMetadata]
@router.get("/databases/{database_id}/table_metadata")
async def agent_database_table_metadata(
database_id: int,
@@ -117,7 +117,7 @@ async def agent_database_table_metadata(
# @ingroup Api
# @BRIEF Generate a SELECT * query template for a table.
# @SIDE_EFFECT HTTP GET to Superset /api/v1/database/{id}/select_star/.
# @RELATION CALLS -> [SupersetClientGetSelectStar]
# @RELATION CALLS -> [Core.Databases.SupersetClientGetSelectStar]
@router.get("/databases/{database_id}/select_star")
async def agent_database_select_star(
database_id: int,
@@ -143,7 +143,7 @@ async def agent_database_select_star(
# @ingroup Api
# @BRIEF Validate SQL syntax for a database without executing.
# @SIDE_EFFECT HTTP POST to Superset /api/v1/database/{id}/validate_sql/.
# @RELATION CALLS -> [SupersetClientValidateSql]
# @RELATION CALLS -> [Core.Databases.SupersetClientValidateSql]
@router.post("/databases/{database_id}/validate_sql")
async def agent_database_validate_sql(
database_id: int,
@@ -164,7 +164,7 @@ async def agent_database_validate_sql(
# @ingroup Api
# @BRIEF Test a database connection URI without creating a database entry.
# @SIDE_EFFECT HTTP POST to Superset /api/v1/database/test_connection/.
# @RELATION CALLS -> [SupersetClientTestDatabaseConnection]
# @RELATION CALLS -> [Core.Databases.SupersetClientTestDatabaseConnection]
@router.post("/databases/test_connection")
async def agent_database_test_connection(
environment_id: str = Query(...),
@@ -257,4 +257,4 @@ async def agent_saved_query_get(
await client.aclose()
# #endregion AgentSuperset.SavedQueryGet
# #endregion AgentSupersetExploreRoutes
# #endregion Api.AgentSupersetExplore.AgentSupersetExploreRoutes

View File

@@ -1,11 +1,11 @@
# #region AssistantApi [C:5] [TYPE Module] [SEMANTICS assistant, api, package, llm, execution]
# #region Api.Init.AssistantApi [C:5] [TYPE Module] [SEMANTICS assistant, api, package, llm, execution]
# @defgroup AssistantApi Module group.
# @BRIEF API routes for LLM assistant command parsing and safe execution orchestration.
# @LAYER API
# @RELATION DEPENDS_ON -> [TaskManager]
# @RELATION DEPENDS_ON -> [AssistantMessageRecord]
# @RELATION DEPENDS_ON -> [AssistantConfirmationRecord]
# @RELATION DEPENDS_ON -> [AssistantAuditRecord]
# @RELATION DEPENDS_ON -> [Core.Manager.TaskManager]
# @RELATION DEPENDS_ON -> [Models.Assistant.AssistantMessageRecord]
# @RELATION DEPENDS_ON -> [Models.Assistant.AssistantConfirmationRecord]
# @RELATION DEPENDS_ON -> [Models.Assistant.AssistantAuditRecord]
# @INVARIANT Risky operations are never executed without valid confirmation token.
# ── Tool registry must be imported first to register all @assistant_tool decorators ──
@@ -136,4 +136,4 @@ __all__ = [
"send_message",
]
# #endregion AssistantApi
# #endregion Api.Init.AssistantApi

View File

@@ -1,10 +1,10 @@
# #region AssistantAdminRoutes [C:5] [TYPE Module] [SEMANTICS assistant, admin, route, audit, conversation]
# #region Api.AdminRoutes.AssistantAdminRoutes [C:5] [TYPE Module] [SEMANTICS assistant, admin, route, audit, conversation]
# @defgroup AssistantApi Module group.
# @BRIEF FastAPI route handlers for assistant admin operations — conversation listing, deletion, history, audit.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantRoutes]
# @RELATION DEPENDS_ON -> [AssistantSchemas]
# @RELATION DEPENDS_ON -> [AssistantHistory]
# @RELATION DEPENDS_ON -> [Api.Routes.AssistantRoutes]
# @RELATION DEPENDS_ON -> [Api.Schemas.AssistantSchemas]
# @RELATION DEPENDS_ON -> [Api.History.AssistantHistory]
# @INVARIANT Audit endpoint requires tasks:READ permission.
# @RATIONALE Admin operations are separated from user-facing assistant routes to enforce strict permission boundaries. The audit endpoint explicitly requires tasks:READ permission, preventing non-admin users from accessing other users' conversation histories and audit decisions.
# @REJECTED Embedding admin endpoints in the main AssistantRoutes module was rejected — it mixes user-facing and admin logic, making permission enforcement fragile and risking privilege escalation through route inclusion order or misconfigured RBAC.
@@ -38,7 +38,7 @@ from ._schemas import (
)
# #region list_conversations [C:2] [TYPE Function]
# #region Api.AdminRoutes.ListConversations [C:2] [TYPE Function]
# @ingroup AssistantApi
# @BRIEF DEPRECATED — replaced by AgentChat.Api.ListConversations.
# Return empty list. Kept for import compatibility.
@@ -54,7 +54,7 @@ async def list_conversations(
):
"""DEPRECATED — use AgentChat.Api.ListConversations instead."""
return {"items": [], "total": 0, "page": page, "page_size": page_size, "has_next": False, "active_total": 0, "archived_total": 0}
# #endregion list_conversations
# #endregion Api.AdminRoutes.ListConversations
@@ -65,7 +65,7 @@ async def list_conversations(
@router.get("/audit")
# #region get_assistant_audit [TYPE Function]
# #region Api.AdminRoutes.GetAssistantAudit [TYPE Function]
# @ingroup AssistantApi
# @BRIEF Return assistant audit decisions for current user from persistent and in-memory stores.
# @PRE User has tasks:READ permission.
@@ -106,7 +106,7 @@ async def get_assistant_audit(
}
# #endregion get_assistant_audit
# #endregion Api.AdminRoutes.GetAssistantAudit
# #endregion AssistantAdminRoutes
# #endregion Api.AdminRoutes.AssistantAdminRoutes

View File

@@ -1,8 +1,8 @@
# #region AssistantCommandParser [C:4] [TYPE Module] [SEMANTICS assistant, command, parser, nlu, intent]
# #region Api.CommandParser.AssistantCommandParser [C:4] [TYPE Module] [SEMANTICS assistant, command, parser, nlu, intent]
# @defgroup AssistantApi Module group.
# @BRIEF Deterministic RU/EN command text parser that converts user messages into intent payloads.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantResolvers]
# @RELATION DEPENDS_ON -> [Api.Resolvers.AssistantResolvers]
# @INVARIANT Every return path includes domain, operation, entities, confidence, risk_level, requires_confirmation.
# @RATIONALE A deterministic regex-based parser is kept as a cold-path fallback because LLM-based intent planning can fail (provider down, API error, ambiguous input). The parser covers 15+ frequent command patterns in RU/EN, ensuring core assistant functionality (status, deploy, migration, backup, help) works without any ML dependency — sub-millisecond response for common patterns.
# @REJECTED Relying solely on LLM intent planning was rejected — LLM providers can be unavailable, slow, or costly for trivial commands ("help", "status"). Full NLP-based parsing (spaCy/BERT) was rejected — it introduces non-deterministic behavior, model downloads, and GPU requirements incompatible with a lightweight fallback.
@@ -18,11 +18,11 @@ from src.core.logger import belief_scope, logger
from ._resolvers import _extract_id, _is_production_env
# #region _parse_command [C:4] [TYPE Function]
# #region Api.CommandParser.ParseCommand [C:4] [TYPE Function]
# @BRIEF Deterministically parse RU/EN command text into intent payload.
# @DATA_CONTRACT Input[message:str, config_manager:ConfigManager] -> Output[Dict[str,Any]{domain,operation,entities,confidence,risk_level,requires_confirmation}]
# @RELATION DEPENDS_ON -> [_extract_id]
# @RELATION DEPENDS_ON -> [_is_production_env]
# @RELATION DEPENDS_ON -> [Api.Resolvers.ExtractId]
# @RELATION DEPENDS_ON -> [Api.Resolvers.IsProductionEnv]
# @SIDE_EFFECT None (pure parsing logic).
# @PRE message contains raw user text and config manager resolves environments.
# @POST Returns intent dict with domain/operation/entities/confidence/risk fields.
@@ -107,7 +107,7 @@ def _parse_command(message: str, config_manager: ConfigManager) -> dict[str, Any
return {'domain': 'unknown', 'operation': 'clarify', 'entities': {}, 'confidence': 0.3, 'risk_level': 'safe', 'requires_confirmation': False}
# #endregion _parse_command
# #endregion Api.CommandParser.ParseCommand
# #endregion AssistantCommandParser
# #endregion Api.CommandParser.AssistantCommandParser

View File

@@ -1,9 +1,9 @@
# #region AssistantDispatch [C:4] [TYPE Module] [SEMANTICS assistant, dispatch, confirm, execution, orchestration]
# #region Api.Dispatch.AssistantDispatch [C:4] [TYPE Module] [SEMANTICS assistant, dispatch, confirm, execution, orchestration]
# @defgroup AssistantApi Module group.
# @BRIEF Intent dispatch engine and backward-compat wrapper around the central tool registry.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [AssistantSchemas]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [Api.Schemas.AssistantSchemas]
# @INVARIANT Unsupported operations are rejected via HTTPException(400).
# @INVARIANT All tool handlers live in _tool_registry — _dispatch is a thin wrapper.
@@ -29,7 +29,7 @@ from ._resolvers import (
from ._schemas import AssistantAction
from ._tool_registry import dispatch as _registry_dispatch
# #region _get_git_service [TYPE Function]
# #region Api.Dispatch.GetGitService [TYPE Function]
# @BRIEF Lazy-init GitService singleton to avoid crash at module import time when /app/storage/ is unavailable.
# @POST Returns GitService instance (created once, cached).
# @SIDE_EFFECT May attempt to create /app/storage/repositories on first call.
@@ -43,10 +43,10 @@ def _get_git_service() -> GitService:
return _git_service_instance
# #endregion _get_git_service
# #endregion Api.Dispatch.GetGitService
# #region _clarification_text_for_intent [C:2] [TYPE Function]
# #region Api.Dispatch.ClarificationTextForIntent [C:2] [TYPE Function]
# @BRIEF Convert technical missing-parameter errors into user-facing clarification prompts.
# @PRE state was classified as needs_clarification for current intent/error combination.
# @POST Returned text is human-readable and actionable for target operation.
@@ -70,10 +70,10 @@ def _clarification_text_for_intent(
return guidance_by_operation.get(operation, detail_text)
# #endregion _clarification_text_for_intent
# #endregion Api.Dispatch.ClarificationTextForIntent
# #region _async_confirmation_summary [C:4] [TYPE Function]
# #region Api.Dispatch.AsyncConfirmationSummary [C:4] [TYPE Function]
# @BRIEF Build human-readable confirmation prompt for an intent before execution.
# @PRE actions is a non-empty list of planned review actions.
# @POST Returns a formatted summary string suitable for display to the user.
@@ -200,10 +200,10 @@ async def _async_confirmation_summary(intent: dict[str, Any], config_manager: Co
return f'Выполнить: {text}. Подтвердите или отмените.'
# #endregion _async_confirmation_summary
# #endregion Api.Dispatch.AsyncConfirmationSummary
# #region _dispatch_intent [C:1] [TYPE Function]
# #region Api.Dispatch.DispatchIntent [C:1] [TYPE Function]
# @BRIEF Backward-compat wrapper around the central tool registry dispatch.
# @DEPRECATED Use `dispatch()` from `_tool_registry` directly.
async def _dispatch_intent(intent: dict[str, Any], current_user: User, task_manager: TaskManager, config_manager: ConfigManager, db: Session) -> tuple[str, str | None, list[AssistantAction]]:
@@ -212,7 +212,7 @@ async def _dispatch_intent(intent: dict[str, Any], current_user: User, task_mana
return await _registry_dispatch(operation, intent, current_user, task_manager, config_manager, db)
# #endregion _dispatch_intent
# #endregion Api.Dispatch.DispatchIntent
# #endregion AssistantDispatch
# #endregion Api.Dispatch.AssistantDispatch

View File

@@ -1,8 +1,8 @@
# #region AssistantHistory [C:2] [TYPE Module] [SEMANTICS assistant, history, audit, persistence, conversation]
# #region Api.History.AssistantHistory [C:2] [TYPE Module] [SEMANTICS assistant, history, audit, persistence, conversation]
# @defgroup AssistantApi Module group.
# @BRIEF Conversation history, audit trail, and confirmation persistence helpers for the assistant API.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantSchemas]
# @RELATION DEPENDS_ON -> [Api.Schemas.AssistantSchemas]
# @INVARIANT Failed persistence attempts always rollback before returning.
from __future__ import annotations
@@ -31,10 +31,10 @@ from ._schemas import (
logger = logger
# #region _append_history [C:2] [TYPE Function]
# #region Api.History.AppendHistory [C:2] [TYPE Function]
# @BRIEF Append conversation message to in-memory history buffer.
# @DATA_CONTRACT Input[user_id,conversation_id,role,text,state?,task_id?,confirmation_id?] -> Output[None]
# @RELATION BINDS_TO -> [CONVERSATIONS]
# @RELATION BINDS_TO -> [Api.Schemas.CONVERSATIONS]
# @SIDE_EFFECT Mutates in-memory CONVERSATIONS store for user conversation history.
# @PRE user_id and conversation_id identify target conversation bucket.
# @POST Message entry is appended to CONVERSATIONS key list.
@@ -65,13 +65,13 @@ def _append_history(
)
# #endregion _append_history
# #endregion Api.History.AppendHistory
# #region _persist_message [C:2] [TYPE Function]
# #region Api.History.PersistMessage [C:2] [TYPE Function]
# @BRIEF Persist assistant/user message record to database.
# @DATA_CONTRACT Input[Session,user_id,conversation_id,role,text,state?,task_id?,confirmation_id?,metadata?] -> Output[None]
# @RELATION DEPENDS_ON -> [AssistantMessageRecord]
# @RELATION DEPENDS_ON -> [Models.Assistant.AssistantMessageRecord]
# @SIDE_EFFECT Writes AssistantMessageRecord rows and commits or rollbacks the DB session.
# @PRE db session is writable and message payload is serializable.
# @POST Message row is committed or persistence failure is logged.
@@ -106,13 +106,13 @@ def _persist_message(
logger.warning(f"[assistant.message][persist_failed] {exc}")
# #endregion _persist_message
# #endregion Api.History.PersistMessage
# #region _audit [C:2] [TYPE Function]
# #region Api.History.Audit [C:2] [TYPE Function]
# @BRIEF Append in-memory audit record for assistant decision trace.
# @DATA_CONTRACT Input[user_id,payload:Dict[str,Any]] -> Output[None]
# @RELATION BINDS_TO -> [ASSISTANT_AUDIT]
# @RELATION BINDS_TO -> [Api.Schemas.ASSISTANTAUDIT]
# @SIDE_EFFECT Mutates in-memory ASSISTANT_AUDIT store and emits structured log event.
# @PRE payload describes decision/outcome fields.
# @POST ASSISTANT_AUDIT list for user contains new timestamped entry.
@@ -126,10 +126,10 @@ def _audit(user_id: str, payload: dict[str, Any]):
logger.info(f"[assistant.audit] {payload}")
# #endregion _audit
# #endregion Api.History.Audit
# #region _persist_audit [C:2] [TYPE Function]
# #region Api.History.PersistAudit [C:2] [TYPE Function]
# @BRIEF Persist structured assistant audit payload in database.
# @PRE db session is writable and payload is JSON-serializable.
# @POST Audit row is committed or failure is logged with rollback.
@@ -153,10 +153,10 @@ def _persist_audit(
logger.warning(f"[assistant.audit][persist_failed] {exc}")
# #endregion _persist_audit
# #endregion Api.History.PersistAudit
# #region _persist_confirmation [C:2] [TYPE Function]
# #region Api.History.PersistConfirmation [C:2] [TYPE Function]
# @BRIEF Persist confirmation token record to database.
# @PRE record contains id/user/intent/dispatch/expiry fields.
# @POST Confirmation row exists in persistent storage.
@@ -180,10 +180,10 @@ def _persist_confirmation(db: Session, record: ConfirmationRecord):
logger.warning(f"[assistant.confirmation][persist_failed] {exc}")
# #endregion _persist_confirmation
# #endregion Api.History.PersistConfirmation
# #region _update_confirmation_state [C:2] [TYPE Function]
# #region Api.History.UpdateConfirmationState [C:2] [TYPE Function]
# @BRIEF Update persistent confirmation token lifecycle state.
# @PRE confirmation_id references existing row.
# @POST State and consumed_at fields are updated when applicable.
@@ -205,10 +205,10 @@ def _update_confirmation_state(db: Session, confirmation_id: str, state: str):
logger.warning(f"[assistant.confirmation][update_failed] {exc}")
# #endregion _update_confirmation_state
# #endregion Api.History.UpdateConfirmationState
# #region _load_confirmation_from_db [C:2] [TYPE Function]
# #region Api.History.LoadConfirmationFromDb [C:2] [TYPE Function]
# @BRIEF Load confirmation token from database into in-memory model.
# @PRE confirmation_id may or may not exist in storage.
# @POST Returns ConfirmationRecord when found, otherwise None.
@@ -234,10 +234,10 @@ def _load_confirmation_from_db(
)
# #endregion _load_confirmation_from_db
# #endregion Api.History.LoadConfirmationFromDb
# #region _ensure_conversation [C:2] [TYPE Function]
# #region Api.History.EnsureConversation [C:2] [TYPE Function]
# @BRIEF Resolve active conversation id in memory or create a new one.
# @PRE user_id identifies current actor.
# @POST Returns stable conversation id and updates USER_ACTIVE_CONVERSATION.
@@ -257,10 +257,10 @@ def _ensure_conversation(user_id: str, conversation_id: str | None) -> str:
return new_id
# #endregion _ensure_conversation
# #endregion Api.History.EnsureConversation
# #region _resolve_or_create_conversation [C:2] [TYPE Function]
# #region Api.History.ResolveOrCreateConversation [C:2] [TYPE Function]
# @BRIEF Resolve active conversation using explicit id, memory cache, or persisted history.
# @PRE user_id and db session are available.
# @POST Returns conversation id and updates USER_ACTIVE_CONVERSATION cache.
@@ -294,10 +294,10 @@ def _resolve_or_create_conversation(
return new_id
# #endregion _resolve_or_create_conversation
# #endregion Api.History.ResolveOrCreateConversation
# #region _cleanup_history_ttl [C:2] [TYPE Function]
# #region Api.History.CleanupHistoryTtl [C:2] [TYPE Function]
# @BRIEF Enforce assistant message retention window by deleting expired rows and in-memory records.
# @PRE db session is available and user_id references current actor scope.
# @POST Messages older than ASSISTANT_MESSAGE_TTL_DAYS are removed from persistence and memory mirrors.
@@ -338,10 +338,10 @@ def _cleanup_history_ttl(db: Session, user_id: str):
CONVERSATIONS.pop(key, None)
# #endregion _cleanup_history_ttl
# #endregion Api.History.CleanupHistoryTtl
# #region _is_conversation_archived [C:2] [TYPE Function]
# #region Api.History.IsConversationArchived [C:2] [TYPE Function]
# @BRIEF Determine archived state for a conversation based on last update timestamp.
# @PRE updated_at can be null for empty conversations.
# @POST Returns True when conversation inactivity exceeds archive threshold.
@@ -354,10 +354,10 @@ def _is_conversation_archived(updated_at: datetime | None) -> bool:
return ref < cutoff
# #endregion _is_conversation_archived
# #endregion Api.History.IsConversationArchived
# #region _coerce_query_bool [C:2] [TYPE Function]
# #region Api.History.CoerceQueryBool [C:2] [TYPE Function]
# @BRIEF Normalize bool-like query values for compatibility in direct handler invocations/tests.
# @PRE value may be bool, string, or FastAPI Query metadata object.
# @POST Returns deterministic boolean flag.
@@ -369,7 +369,7 @@ def _coerce_query_bool(value: Any) -> bool:
return False
# #endregion _coerce_query_bool
# #endregion Api.History.CoerceQueryBool
# #endregion AssistantHistory
# #endregion Api.History.AssistantHistory

View File

@@ -1,10 +1,10 @@
# #region AssistantLlmPlanner [C:5] [TYPE Module] [SEMANTICS assistant, llm, planner, tool, catalog]
# #region Api.LlmPlanner.AssistantLlmPlanner [C:5] [TYPE Module] [SEMANTICS assistant, llm, planner, tool, catalog]
# @defgroup AssistantApi Module group.
# @BRIEF LLM-based intent planning, tool catalog construction, and authorization for the assistant API.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantSchemas]
# @RELATION DEPENDS_ON -> [AssistantResolvers]
# @RELATION DISPATCHES -> [AssistantLlmPlannerIntent]
# @RELATION DEPENDS_ON -> [Api.Schemas.AssistantSchemas]
# @RELATION DEPENDS_ON -> [Api.Resolvers.AssistantResolvers]
# @RELATION DISPATCHES -> [Api.LlmPlannerIntent.AssistantLlmPlannerIntent]
# @PRE Assistant routes initialized, user authenticated
# @POST LLM tool catalog filtered and returned
# @INVARIANT Tool catalog is filtered by user permissions before being sent to LLM.
@@ -27,7 +27,7 @@ from ._tool_registry import (
)
# #region _build_tool_catalog [C:2] [TYPE Function]
# #region Api.LlmPlanner.BuildToolCatalog [C:2] [TYPE Function]
# @ingroup AssistantApi
# @BRIEF Build tool catalog from the central registry, filtered by user permissions.
# @PRE current_user is authenticated; config/db are available.
@@ -41,10 +41,10 @@ def _build_tool_catalog(
return get_catalog(current_user, config_manager, db)
# #endregion _build_tool_catalog
# #endregion Api.LlmPlanner.BuildToolCatalog
# #region _coerce_intent_entities [C:2] [TYPE Function]
# #region Api.LlmPlanner.CoerceIntentEntities [C:2] [TYPE Function]
# @ingroup AssistantApi
# @BRIEF Normalize intent entity value types from LLM output to route-compatible values.
# @PRE intent contains entities dict or missing entities.
@@ -64,7 +64,7 @@ def _coerce_intent_entities(intent: dict[str, Any]) -> dict[str, Any]:
return intent
# #endregion _coerce_intent_entities
# #endregion Api.LlmPlanner.CoerceIntentEntities
# #endregion AssistantLlmPlanner
# #endregion Api.LlmPlanner.AssistantLlmPlanner

View File

@@ -1,9 +1,9 @@
# #region AssistantLlmPlannerIntent [C:5] [TYPE Module] [SEMANTICS assistant, llm, intent, planning, authorization]
# #region Api.LlmPlannerIntent.AssistantLlmPlannerIntent [C:5] [TYPE Module] [SEMANTICS assistant, llm, intent, planning, authorization]
# @defgroup AssistantApi Module group.
# @BRIEF LLM-based intent planning and authorization for the assistant API — separated from tool catalog.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantLlmPlanner]
# @RELATION DEPENDS_ON -> [AssistantResolvers]
# @RELATION DEPENDS_ON -> [Api.LlmPlanner.AssistantLlmPlanner]
# @RELATION DEPENDS_ON -> [Api.Resolvers.AssistantResolvers]
# @PRE Assistant routes initialized, user authenticated
# @POST Intent planning registered with confirmation gate
# @INVARIANT Production deployments always require confirmation.
@@ -37,7 +37,7 @@ from ._resolvers import (
from ._tool_registry import _check_any_permission, get_permission_checks
# #region _plan_intent_with_llm [C:2] [TYPE Function]
# #region Api.LlmPlannerIntent.PlanIntentWithLlm [C:2] [TYPE Function]
# @ingroup AssistantApi
# @BRIEF Use active LLM provider to select best tool/operation from dynamic catalog.
# @PRE tools list contains allowed operations for current user.
@@ -160,10 +160,10 @@ async def _plan_intent_with_llm(
return intent
# #endregion _plan_intent_with_llm
# #endregion Api.LlmPlannerIntent.PlanIntentWithLlm
# #region _authorize_intent [C:2] [TYPE Function]
# #region Api.LlmPlannerIntent.AuthorizeIntent [C:2] [TYPE Function]
# @ingroup AssistantApi
# @BRIEF Validate user permissions for parsed intent before confirmation/dispatch.
# @PRE intent.operation is present for known assistant command domains.
@@ -175,7 +175,7 @@ def _authorize_intent(intent: dict[str, Any], current_user: User):
_check_any_permission(current_user, checks)
# #endregion _authorize_intent
# #endregion Api.LlmPlannerIntent.AuthorizeIntent
# #endregion AssistantLlmPlannerIntent
# #endregion Api.LlmPlannerIntent.AssistantLlmPlannerIntent

View File

@@ -1,9 +1,9 @@
# #region AssistantResolvers [C:2] [TYPE Module] [SEMANTICS assistant, resolver, lookup, environment, mapper]
# #region Api.Resolvers.AssistantResolvers [C:2] [TYPE Module] [SEMANTICS assistant, resolver, lookup, environment, mapper]
# @defgroup AssistantApi Module group.
# @BRIEF Environment, dashboard, provider, and task resolution utilities for the assistant API.
# @LAYER API
# @RELATION DEPENDS_ON -> [ConfigManager]
# @RELATION DEPENDS_ON -> [SupersetClient]
# @RELATION DEPENDS_ON -> [Core.ConfigManager]
# @RELATION DEPENDS_ON -> [Core.Init.SupersetClient]
# @INVARIANT Resolution functions never raise; they return None on failure.
from __future__ import annotations
@@ -22,7 +22,7 @@ from src.services.llm_provider import LLMProviderService
logger = cast(Any, logger)
# #region _extract_id [C:2] [TYPE Function]
# #region Api.Resolvers.ExtractId [C:2] [TYPE Function]
# @BRIEF Extract first regex match group from text by ordered pattern list.
# @PRE patterns contain at least one capture group.
# @POST Returns first matched token or None.
@@ -34,9 +34,9 @@ def _extract_id(text: str, patterns: list[str]) -> str | None:
return None
# #endregion _extract_id
# #endregion Api.Resolvers.ExtractId
# #region _resolve_env_id [C:2] [TYPE Function]
# #region Api.Resolvers.ResolveEnvId [C:2] [TYPE Function]
# @BRIEF Resolve environment identifier/name token to canonical environment id.
# @PRE config_manager provides environment list.
# @POST Returns matched environment id or None.
@@ -54,9 +54,9 @@ def _resolve_env_id(
return None
# #endregion _resolve_env_id
# #endregion Api.Resolvers.ResolveEnvId
# #region _is_production_env [C:2] [TYPE Function]
# #region Api.Resolvers.IsProductionEnv [C:2] [TYPE Function]
# @BRIEF Determine whether environment token resolves to production-like target.
# @PRE config_manager provides environments or token text is provided.
# @POST Returns True for production/prod synonyms, else False.
@@ -72,9 +72,9 @@ def _is_production_env(token: str | None, config_manager: ConfigManager) -> bool
return "prod" in target or "production" in target or "прод" in target
# #endregion _is_production_env
# #endregion Api.Resolvers.IsProductionEnv
# #region _resolve_provider_id [C:2] [TYPE Function]
# #region Api.Resolvers.ResolveProviderId [C:2] [TYPE Function]
# @BRIEF Resolve provider token to provider id with active/default fallback.
# @PRE db session can load provider list through LLMProviderService.
# @POST Returns provider id or None when no providers configured.
@@ -108,9 +108,9 @@ def _resolve_provider_id(
return active.id if active else providers[0].id
# #endregion _resolve_provider_id
# #endregion Api.Resolvers.ResolveProviderId
# #region _get_default_environment_id [C:2] [TYPE Function]
# #region Api.Resolvers.GetDefaultEnvironmentId [C:2] [TYPE Function]
# @BRIEF Resolve default environment id from settings or first configured environment.
# @PRE config_manager returns environments list.
# @POST Returns default environment id or None when environment list is empty.
@@ -132,9 +132,9 @@ def _get_default_environment_id(config_manager: ConfigManager) -> str | None:
return explicit_default or configured[0].id
# #endregion _get_default_environment_id
# #endregion Api.Resolvers.GetDefaultEnvironmentId
# #region _resolve_dashboard_id_by_ref [C:2] [TYPE Function]
# #region Api.Resolvers.ResolveDashboardIdByRef [C:2] [TYPE Function]
# @BRIEF Resolve dashboard id by title or slug reference in selected environment.
# @PRE dashboard_ref is a non-empty string-like token.
# @POST Returns dashboard id when uniquely matched, otherwise None.
@@ -184,9 +184,9 @@ async def _resolve_dashboard_id_by_ref(
return None
# #endregion _resolve_dashboard_id_by_ref
# #endregion Api.Resolvers.ResolveDashboardIdByRef
# #region _resolve_dashboard_id_entity [C:2] [TYPE Function]
# #region Api.Resolvers.ResolveDashboardIdEntity [C:2] [TYPE Function]
# @BRIEF Resolve dashboard id from intent entities using numeric id or dashboard_ref fallback.
# @PRE entities may contain dashboard_id as int/str and optional dashboard_ref.
# @POST Returns resolved dashboard id or None when ambiguous/unresolvable.
@@ -225,9 +225,9 @@ async def _resolve_dashboard_id_entity(
return await _resolve_dashboard_id_by_ref(str(dashboard_ref), env_id, config_manager)
# #endregion _resolve_dashboard_id_entity
# #endregion Api.Resolvers.ResolveDashboardIdEntity
# #region _get_environment_name_by_id [C:2] [TYPE Function]
# #region Api.Resolvers.GetEnvironmentNameById [C:2] [TYPE Function]
# @BRIEF Resolve human-readable environment name by id.
# @PRE environment id may be None.
# @POST Returns matching environment name or fallback id.
@@ -242,9 +242,9 @@ def _get_environment_name_by_id(
return env.name if env else env_id
# #endregion _get_environment_name_by_id
# #endregion Api.Resolvers.GetEnvironmentNameById
# #region _extract_result_deep_links [C:2] [TYPE Function]
# #region Api.Resolvers.ExtractResultDeepLinks [C:2] [TYPE Function]
# @BRIEF Build deep-link actions to verify task result from assistant chat.
# @PRE task object is available.
# @POST Returns zero or more assistant actions for dashboard open/diff.
@@ -315,9 +315,9 @@ def _extract_result_deep_links(
return actions
# #endregion _extract_result_deep_links
# #endregion Api.Resolvers.ExtractResultDeepLinks
# #region _build_task_observability_summary [C:2] [TYPE Function]
# #region Api.Resolvers.BuildTaskObservabilitySummary [C:2] [TYPE Function]
# @BRIEF Build compact textual summary for completed tasks to reduce "black box" effect.
# @PRE task may contain plugin-specific result payload.
# @POST Returns non-empty summary line for known task types or empty string fallback.
@@ -381,6 +381,6 @@ def _build_task_observability_summary(task: Any, config_manager: ConfigManager)
return ""
# #endregion _build_task_observability_summary
# #endregion Api.Resolvers.BuildTaskObservabilitySummary
# #endregion AssistantResolvers
# #endregion Api.Resolvers.AssistantResolvers

View File

@@ -1,13 +1,13 @@
# #region AssistantRoutes [C:5] [TYPE Module] [SEMANTICS assistant, api, route, chat, execution]
# #region Api.Routes.AssistantRoutes [C:5] [TYPE Module] [SEMANTICS assistant, api, route, chat, execution]
# @defgroup AssistantApi Module group.
# @BRIEF FastAPI route handlers for the assistant API — message sending, confirmation, conversation management.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantSchemas]
# @RELATION DEPENDS_ON -> [AssistantHistory]
# @RELATION DEPENDS_ON -> [AssistantCommandParser]
# @RELATION DEPENDS_ON -> [AssistantLlmPlanner]
# @RELATION DEPENDS_ON -> [AssistantDispatch]
# @RELATION DISPATCHES -> [AssistantAdminRoutes]
# @RELATION DEPENDS_ON -> [Api.Schemas.AssistantSchemas]
# @RELATION DEPENDS_ON -> [Api.History.AssistantHistory]
# @RELATION DEPENDS_ON -> [Api.CommandParser.AssistantCommandParser]
# @RELATION DEPENDS_ON -> [Api.LlmPlanner.AssistantLlmPlanner]
# @RELATION DEPENDS_ON -> [Api.Dispatch.AssistantDispatch]
# @RELATION DISPATCHES -> [Api.AdminRoutes.AssistantAdminRoutes]
# @INVARIANT Risky operations are never executed without valid confirmation token.
# @RATIONALE Confirmation gating is required because the assistant executes arbitrary operations (including destructive ones) based on natural language parsing. The separation of intention detection → authorization → confirmation → execution prevents accidental or malicious operations triggered by misparsed user input.
# @REJECTED Auto-executing all parsed intents without confirmation was rejected — the assistant parses natural language which frequently misinterprets ambiguous user intent; requiring explicit user confirmation for non-safe operations prevents unintended destructive actions and provides an audit trail of user consent.
@@ -62,16 +62,16 @@ router = APIRouter(tags=["Assistant"])
@router.post("/messages", response_model=AssistantMessageResponse)
# #region send_message [C:5] [TYPE Function]
# #region Api.Routes.SendMessage [C:5] [TYPE Function]
# @ingroup AssistantApi
# @BRIEF Parse assistant command, enforce safety gates, and dispatch executable intent.
# @DATA_CONTRACT Input[AssistantMessageRequest,User,TaskManager,ConfigManager,Session] -> Output[AssistantMessageResponse]
# @RELATION DEPENDS_ON -> [_plan_intent_with_llm]
# @RELATION DEPENDS_ON -> [_parse_command]
# @RELATION DEPENDS_ON -> [dispatch]
# @RELATION DEPENDS_ON -> [_append_history]
# @RELATION DEPENDS_ON -> [_persist_message]
# @RELATION DEPENDS_ON -> [_audit]
# @RELATION DEPENDS_ON -> [Api.LlmPlannerIntent.PlanIntentWithLlm]
# @RELATION DEPENDS_ON -> [Api.CommandParser.ParseCommand]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.Dispatch]
# @RELATION DEPENDS_ON -> [Api.History.AppendHistory]
# @RELATION DEPENDS_ON -> [Api.History.PersistMessage]
# @RELATION DEPENDS_ON -> [Api.History.Audit]
# @SIDE_EFFECT Persists chat/audit state, mutates in-memory conversation and confirmation stores, and may create confirmation records.
# @PRE Authenticated user is available and message text is non-empty.
# @POST Response state is one of clarification/confirmation/started/success/denied/failed.
@@ -152,13 +152,13 @@ async def send_message(request: AssistantMessageRequest, current_user: User=Depe
return AssistantMessageResponse(conversation_id=conversation_id, response_id=str(uuid.uuid4()), state=state, text=text, intent=intent, actions=[AssistantAction(type='rephrase', label='Rephrase command')] if state == 'needs_clarification' else [], created_at=datetime.now(UTC))
# #endregion send_message
# #endregion Api.Routes.SendMessage
@router.post(
"/confirmations/{confirmation_id}/confirm", response_model=AssistantMessageResponse
)
# #region confirm_operation [C:2] [TYPE Function]
# #region Api.Routes.ConfirmOperation [C:2] [TYPE Function]
# @ingroup AssistantApi
# @BRIEF Execute previously requested risky operation after explicit user confirmation.
# @PRE confirmation_id exists, belongs to current user, is pending, and not expired.
@@ -241,13 +241,13 @@ async def confirm_operation(
# #endregion confirm_operation
# #endregion Api.Routes.ConfirmOperation
@router.post(
"/confirmations/{confirmation_id}/cancel", response_model=AssistantMessageResponse
)
# #region cancel_operation [C:2] [TYPE Function]
# #region Api.Routes.CancelOperation [C:2] [TYPE Function]
# @ingroup AssistantApi
# @BRIEF Cancel pending risky operation and mark confirmation token as cancelled.
# @PRE confirmation_id exists, belongs to current user, and is still pending.
@@ -318,7 +318,7 @@ async def cancel_operation(
# #endregion cancel_operation
# #endregion Api.Routes.CancelOperation
# #endregion AssistantRoutes
# #endregion Api.Routes.AssistantRoutes

View File

@@ -1,9 +1,9 @@
# #region AssistantSchemas [C:2] [TYPE Module] [SEMANTICS assistant, pydantic, schema, store, permission]
# #region Api.Schemas.AssistantSchemas [C:2] [TYPE Module] [SEMANTICS assistant, pydantic, schema, store, permission]
# @defgroup AssistantApi Module group.
# @BRIEF Pydantic models, in-memory stores, and permission mappings for the assistant API.
# @LAYER API
# @RELATION CALLED_BY -> [AssistantHistory]
# @RELATION CALLED_BY -> [AssistantHistory]
# @RELATION CALLED_BY -> [Api.History.AssistantHistory]
# @RELATION CALLED_BY -> [Api.History.AssistantHistory]
# @INVARIANT In-memory stores are module-level singletons shared across the assistant package.
# @RATIONALE In-memory stores documented with NOTE about restart loss. ASSISTANT_ARCHIVE_AFTER_DAYS and ASSISTANT_MESSAGE_TTL_DAYS kept as module-level constants with TODO for config migration — Pydantic schemas module should not depend on ConfigManager for architectural purity.
@@ -15,10 +15,10 @@ from typing import Any
from pydantic import BaseModel, Field
# #region AssistantMessageRequest [C:1] [TYPE Class]
# #region Api.Schemas.AssistantMessageRequest [C:1] [TYPE Class]
# @BRIEF Input payload for assistant message endpoint.
# @DATA_CONTRACT Input[conversation_id?:str, message:str(1..4000)] -> Output[AssistantMessageRequest]
# @RELATION CALLED_BY -> [send_message]
# @RELATION CALLED_BY -> [Api.Routes.SendMessage]
# @SIDE_EFFECT None (schema declaration only).
# @PRE message length is within accepted bounds.
# @POST Request object provides message text and optional conversation binding.
@@ -28,13 +28,13 @@ class AssistantMessageRequest(BaseModel):
message: str = Field(..., min_length=1, max_length=4000)
# #endregion AssistantMessageRequest
# #endregion Api.Schemas.AssistantMessageRequest
# #region AssistantAction [C:1] [TYPE Class]
# #region Api.Schemas.AssistantAction [C:1] [TYPE Class]
# @BRIEF UI action descriptor returned with assistant responses.
# @DATA_CONTRACT Input[type:str, label:str, target?:str] -> Output[AssistantAction]
# @RELATION CALLED_BY -> [AssistantMessageResponse]
# @RELATION CALLED_BY -> [Api.Schemas.AssistantMessageResponse]
# @SIDE_EFFECT None (schema declaration only).
# @PRE type and label are provided by orchestration logic.
# @POST Action can be rendered as button on frontend.
@@ -45,15 +45,15 @@ class AssistantAction(BaseModel):
target: str | None = None
# #endregion AssistantAction
# #endregion Api.Schemas.AssistantAction
# #region AssistantMessageResponse [C:1] [TYPE Class]
# #region Api.Schemas.AssistantMessageResponse [C:1] [TYPE Class]
# @BRIEF Output payload contract for assistant interaction endpoints.
# @DATA_CONTRACT Input[conversation_id,response_id,state,text,intent?,confirmation_id?,task_id?,actions[],created_at] -> Output[AssistantMessageResponse]
# @RELATION CALLED_BY -> [send_message]
# @RELATION CALLED_BY -> [confirm_operation]
# @RELATION CALLED_BY -> [cancel_operation]
# @RELATION CALLED_BY -> [Api.Routes.SendMessage]
# @RELATION CALLED_BY -> [Api.Routes.ConfirmOperation]
# @RELATION CALLED_BY -> [Api.Routes.CancelOperation]
# @SIDE_EFFECT None (schema declaration only).
# @PRE Response includes deterministic state and text.
# @POST Payload may include task_id/confirmation_id/actions for UI follow-up.
@@ -70,15 +70,15 @@ class AssistantMessageResponse(BaseModel):
created_at: datetime
# #endregion AssistantMessageResponse
# #endregion Api.Schemas.AssistantMessageResponse
# #region ConfirmationRecord [C:1] [TYPE Class]
# #region Api.Schemas.ConfirmationRecord [C:1] [TYPE Class]
# @BRIEF In-memory confirmation token model for risky operation dispatch.
# @DATA_CONTRACT Input[id,user_id,conversation_id,intent,dispatch,expires_at,state?,created_at] -> Output[ConfirmationRecord]
# @RELATION CALLED_BY -> [send_message]
# @RELATION CALLED_BY -> [confirm_operation]
# @RELATION CALLED_BY -> [cancel_operation]
# @RELATION CALLED_BY -> [Api.Routes.SendMessage]
# @RELATION CALLED_BY -> [Api.Routes.ConfirmOperation]
# @RELATION CALLED_BY -> [Api.Routes.CancelOperation]
# @SIDE_EFFECT None (schema declaration only).
# @PRE intent/dispatch/user_id are populated at confirmation request time.
# @POST Record tracks lifecycle state and expiry timestamp.
@@ -94,19 +94,19 @@ class ConfirmationRecord(BaseModel):
created_at: datetime
# #endregion ConfirmationRecord
# #endregion Api.Schemas.ConfirmationRecord
# --- In-memory stores ---
# #region CONVERSATIONS [C:1] [TYPE Constant]
# #region Api.Schemas.CONVERSATIONS [C:1] [TYPE Constant]
# @BRIEF In-memory conversation history store: {(user_id, conversation_id) -> [message_records]}
# #endregion CONVERSATIONS
# #endregion Api.Schemas.CONVERSATIONS
CONVERSATIONS: dict[tuple[str, str], list[dict[str, Any]]] = {}
# #region ASSISTANT_AUDIT [C:1] [TYPE Constant]
# #region Api.Schemas.ASSISTANTAUDIT [C:1] [TYPE Constant]
# @BRIEF In-memory assistant audit trail: {user_id -> [audit_records]}. Volatile — lost on restart.
# #endregion ASSISTANT_AUDIT
# #endregion Api.Schemas.ASSISTANTAUDIT
USER_ACTIVE_CONVERSATION: dict[str, str] = {}
CONFIRMATIONS: dict[str, ConfirmationRecord] = {}
ASSISTANT_AUDIT: dict[str, list[dict[str, Any]]] = {}
@@ -136,4 +136,4 @@ INTENT_PERMISSION_CHECKS: dict[str, list[tuple[str, str]]] = {
}
# #endregion AssistantSchemas
# #endregion Api.Schemas.AssistantSchemas

View File

@@ -1,9 +1,9 @@
# #region AssistantToolBackup [C:3] [TYPE Module] [SEMANTICS assistant, tool, backup]
# #region Api.ToolBackup.AssistantToolBackup [C:3] [TYPE Module] [SEMANTICS assistant, tool, backup]
# @defgroup AssistantApi Module group.
# @BRIEF Handler for the "run_backup" tool — run backup for environment or specific dashboard.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [TaskManager]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [Core.Manager.TaskManager]
from __future__ import annotations
@@ -26,7 +26,7 @@ from ._schemas import AssistantAction
from ._tool_registry import _check_any_permission, assistant_tool
# #region handle_run_backup [C:3] [TYPE Function]
# #region Api.ToolBackup.HandleRunBackup [C:3] [TYPE Function]
# @ingroup AssistantApi
@assistant_tool(
operation="run_backup",
@@ -98,5 +98,5 @@ async def handle_run_backup(
return (f"Бэкап запущен. task_id={task.id}", task.id, actions)
# #endregion handle_run_backup
# #endregion AssistantToolBackup
# #endregion Api.ToolBackup.HandleRunBackup
# #endregion Api.ToolBackup.AssistantToolBackup

View File

@@ -1,8 +1,8 @@
# #region AssistantToolCapabilities [C:3] [TYPE Module] [SEMANTICS assistant, tool, capabilities, catalog]
# #region Api.ToolCapabilities.AssistantToolCapabilities [C:3] [TYPE Module] [SEMANTICS assistant, tool, capabilities, catalog]
# @defgroup AssistantApi Module group.
# @BRIEF Handler for the "show_capabilities" tool — lists available assistant commands and examples.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.AssistantToolRegistry]
from __future__ import annotations
@@ -31,7 +31,7 @@ _HUMAN_LABELS: dict[str, str] = {
}
# #region handle_show_capabilities [C:2] [TYPE Function]
# #region Api.ToolCapabilities.HandleShowCapabilities [C:2] [TYPE Function]
# @ingroup AssistantApi
@assistant_tool(
operation="show_capabilities",
@@ -65,5 +65,5 @@ async def handle_show_capabilities(
return (text, None, [])
# #endregion handle_show_capabilities
# #endregion AssistantToolCapabilities
# #endregion Api.ToolCapabilities.HandleShowCapabilities
# #endregion Api.ToolCapabilities.AssistantToolCapabilities

View File

@@ -1,9 +1,9 @@
# #region AssistantToolCommit [C:3] [TYPE Module] [SEMANTICS assistant, tool, git, commit]
# #region Api.ToolCommit.AssistantToolCommit [C:3] [TYPE Module] [SEMANTICS assistant, tool, git, commit]
# @defgroup AssistantApi Module group.
# @BRIEF Handler for the "commit_changes" tool — commit dashboard repository changes.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [GitService]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [Services.Init.GitService]
from __future__ import annotations
@@ -24,7 +24,7 @@ from ._tool_registry import _check_any_permission, assistant_tool
from ._dispatch import _get_git_service
# #region handle_commit_changes [C:3] [TYPE Function]
# #region Api.ToolCommit.HandleCommitChanges [C:3] [TYPE Function]
# @ingroup AssistantApi
@assistant_tool(
operation="commit_changes",
@@ -54,5 +54,5 @@ async def handle_commit_changes(
return ("Коммит выполнен успешно.", None, [])
# #endregion handle_commit_changes
# #endregion AssistantToolCommit
# #endregion Api.ToolCommit.HandleCommitChanges
# #endregion Api.ToolCommit.AssistantToolCommit

View File

@@ -1,9 +1,9 @@
# #region AssistantToolCreateBranch [C:3] [TYPE Module] [SEMANTICS assistant, tool, git, branch]
# #region Api.ToolCreateBranch.AssistantToolCreateBranch [C:3] [TYPE Module] [SEMANTICS assistant, tool, git, branch]
# @defgroup AssistantApi Module group.
# @BRIEF Handler for the "create_branch" tool — create git branch for a dashboard.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [GitService]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [Services.Init.GitService]
from __future__ import annotations
@@ -24,7 +24,7 @@ from ._tool_registry import _check_any_permission, assistant_tool
from ._dispatch import _get_git_service
# #region handle_create_branch [C:3] [TYPE Function]
# #region Api.ToolCreateBranch.HandleCreateBranch [C:3] [TYPE Function]
# @ingroup AssistantApi
@assistant_tool(
operation="create_branch",
@@ -57,5 +57,5 @@ async def handle_create_branch(
return (f"Ветка `{branch_name}` создана для дашборда {dashboard_id}.", None, [])
# #endregion handle_create_branch
# #endregion AssistantToolCreateBranch
# #endregion Api.ToolCreateBranch.HandleCreateBranch
# #endregion Api.ToolCreateBranch.AssistantToolCreateBranch

View File

@@ -1,9 +1,9 @@
# #region AssistantToolDeploy [C:3] [TYPE Module] [SEMANTICS assistant, tool, git, deploy]
# #region Api.ToolDeploy.AssistantToolDeploy [C:3] [TYPE Module] [SEMANTICS assistant, tool, git, deploy]
# @defgroup AssistantApi Module group.
# @BRIEF Handler for the "deploy_dashboard" tool — deploy dashboard to target environment.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [TaskManager]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [Core.Manager.TaskManager]
from __future__ import annotations
@@ -22,7 +22,7 @@ from ._schemas import AssistantAction
from ._tool_registry import _check_any_permission, assistant_tool
# #region handle_deploy_dashboard [C:3] [TYPE Function]
# #region Api.ToolDeploy.HandleDeployDashboard [C:3] [TYPE Function]
# @ingroup AssistantApi
@assistant_tool(
operation="deploy_dashboard",
@@ -75,5 +75,5 @@ async def handle_deploy_dashboard(
)
# #endregion handle_deploy_dashboard
# #endregion AssistantToolDeploy
# #endregion Api.ToolDeploy.HandleDeployDashboard
# #endregion Api.ToolDeploy.AssistantToolDeploy

View File

@@ -1,9 +1,9 @@
# #region AssistantToolHealthSummary [C:3] [TYPE Module] [SEMANTICS assistant, tool, health, summary]
# #region Api.ToolHealthSummary.AssistantToolHealthSummary [C:3] [TYPE Module] [SEMANTICS assistant, tool, health, summary]
# @defgroup AssistantApi Module group.
# @BRIEF Handler for the "get_health_summary" tool — get summary of dashboard health.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [HealthService]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [Services.HealthServiceClass]
from __future__ import annotations
@@ -22,7 +22,7 @@ from ._schemas import AssistantAction
from ._tool_registry import assistant_tool
# #region handle_get_health_summary [C:3] [TYPE Function]
# #region Api.ToolHealthSummary.HandleGetHealthSummary [C:3] [TYPE Function]
# @ingroup AssistantApi
@assistant_tool(
operation="get_health_summary",
@@ -79,5 +79,5 @@ async def handle_get_health_summary(
return (text, None, actions[:5])
# #endregion handle_get_health_summary
# #endregion AssistantToolHealthSummary
# #endregion Api.ToolHealthSummary.HandleGetHealthSummary
# #endregion Api.ToolHealthSummary.AssistantToolHealthSummary

View File

@@ -1,8 +1,8 @@
# #region AssistantToolListEnvironments [C:2] [TYPE Module] [SEMANTICS assistant, tool, environments, list]
# #region Api.ToolListEnvironments.AssistantToolListEnvironments [C:2] [TYPE Module] [SEMANTICS assistant, tool, environments, list]
# @defgroup AssistantApi Module group.
# @BRIEF Handler for the "list_environments" tool — show available Superset environments.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.AssistantToolRegistry]
from __future__ import annotations
@@ -19,7 +19,7 @@ from ._schemas import AssistantAction
from ._tool_registry import assistant_tool
# #region handle_list_environments [C:2] [TYPE Function]
# #region Api.ToolListEnvironments.HandleListEnvironments [C:2] [TYPE Function]
# @ingroup AssistantApi
@assistant_tool(
operation="list_environments",
@@ -63,5 +63,5 @@ async def handle_list_environments(
return ("\n".join(lines), None, actions)
# #endregion handle_list_environments
# #endregion AssistantToolListEnvironments
# #endregion Api.ToolListEnvironments.HandleListEnvironments
# #endregion Api.ToolListEnvironments.AssistantToolListEnvironments

View File

@@ -1,9 +1,9 @@
# #region AssistantToolLlm [C:2] [TYPE Module] [SEMANTICS assistant, tool, llm, providers, status]
# #region Api.ToolLlm.AssistantToolLlm [C:2] [TYPE Module] [SEMANTICS assistant, tool, llm, providers, status]
# @defgroup AssistantApi Module group.
# @BRIEF Handlers for LLM operations — list providers and check LLM status.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [LLMProviderService]
# @RELATION DEPENDS_ON -> [Api.ToolRegistry.AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [Services.LlmProvider.LLMProviderService]
from __future__ import annotations
@@ -21,7 +21,7 @@ from ._schemas import AssistantAction
from ._tool_registry import assistant_tool
# #region handle_list_llm_providers [C:2] [TYPE Function]
# #region Api.ToolLlm.HandleListLlmProviders [C:2] [TYPE Function]
# @ingroup AssistantApi
@assistant_tool(
operation="list_llm_providers",
@@ -67,10 +67,10 @@ async def handle_list_llm_providers(
)
# #endregion handle_list_llm_providers
# #endregion Api.ToolLlm.HandleListLlmProviders
# #region handle_get_llm_status [C:2] [TYPE Function]
# #region Api.ToolLlm.HandleGetLlmStatus [C:2] [TYPE Function]
# @ingroup AssistantApi
@assistant_tool(
operation="get_llm_status",
@@ -126,5 +126,5 @@ async def handle_get_llm_status(
)
# #endregion handle_get_llm_status
# #endregion AssistantToolLlm
# #endregion Api.ToolLlm.HandleGetLlmStatus
# #endregion Api.ToolLlm.AssistantToolLlm

Some files were not shown because too many files have changed in this diff Show More