diff --git a/.ai/ROOT.md b/.ai/ROOT.md index 9c4c29b..c01585a 100644 --- a/.ai/ROOT.md +++ b/.ai/ROOT.md @@ -1,7 +1,7 @@ # [DEF:Project_Knowledge_Map:Root] # @TIER: CRITICAL # @PURPOSE: Global navigation map for AI-Agent (GRACE Knowledge Graph). -# @LAST_UPDATE: 2026-02-19 +# @LAST_UPDATE: 2026-02-20 ## 1. SYSTEM STANDARDS (Rules of the Game) Strict policies and formatting rules. @@ -26,6 +26,8 @@ Use these for code generation (Style Transfer). * Ref: `.ai/shots/frontend_component.svelte` -> `[DEF:Shot:Svelte_Component]` * **Plugin Module:** Reference implementation of a task plugin. * Ref: `.ai/shots/plugin_example.py` -> `[DEF:Shot:Plugin_Example]` +* **Critical Module:** Core banking transaction processor with ACID guarantees. + * Ref: `.ai/shots/critical_module.py` -> `[DEF:Shot:Critical_Module]` ## 3. DOMAIN MAP (Modules) * **Project Map:** `.ai/PROJECT_MAP.md` -> `[DEF:Project_Map]` diff --git a/.ai/shots/backend_route.py b/.ai/shots/backend_route.py index 76d829d..b5239e7 100644 --- a/.ai/shots/backend_route.py +++ b/.ai/shots/backend_route.py @@ -1,14 +1,18 @@ -# [DEF:Shot:FastAPI_Route:Example] +# [DEF:BackendRouteShot:Module] +# @TIER: STANDARD +# @SEMANTICS: Route, Task, API, Async # @PURPOSE: Reference implementation of a task-based route using GRACE-Poly. +# @LAYER: Interface (API) # @RELATION: IMPLEMENTS -> [DEF:Std:API_FastAPI] +# @INVARIANT: TaskManager must be available in dependency graph. -from typing import List, Dict, Any, Optional +from typing import Dict, Any from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from ...core.logger import belief_scope from ...core.task_manager import TaskManager, Task from ...core.config_manager import ConfigManager -from ...dependencies import get_task_manager, get_config_manager, has_permission, get_current_user +from ...dependencies import get_task_manager, get_config_manager, get_current_user router = APIRouter() @@ -21,37 +25,41 @@ class CreateTaskRequest(BaseModel): # @PURPOSE: Create and start a new task using TaskManager. Non-blocking. # @PARAM: request (CreateTaskRequest) - Plugin and params. # @PARAM: task_manager (TaskManager) - Async task executor. -# @PARAM: config (ConfigManager) - Centralized configuration. -# @PRE: plugin_id must exist; config must be initialized. +# @PRE: plugin_id must match a registered plugin. # @POST: A new task is spawned; Task ID returned immediately. +# @SIDE_EFFECT: Writes to DB, Trigger background worker. async def create_task( request: CreateTaskRequest, task_manager: TaskManager = Depends(get_task_manager), config: ConfigManager = Depends(get_config_manager), current_user = Depends(get_current_user) ): - # RBAC: Dynamic permission check - has_permission(f"plugin:{request.plugin_id}", "EXECUTE")(current_user) - + # Context Logging with belief_scope("create_task"): try: - # 1. Action: Resolve setting using ConfigManager (Example) + # 1. Action: Configuration Resolution timeout = config.get("TASKS_DEFAULT_TIMEOUT", 3600) - # 2. Action: Spawn async task via TaskManager + # 2. Action: Spawn async task # @RELATION: CALLS -> task_manager.create_task task = await task_manager.create_task( plugin_id=request.plugin_id, params={**request.params, "timeout": timeout} ) return task + + except ValueError as e: + # 3. Recovery: Domain logic error mapping + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e) + ) except Exception as e: - # Evaluation: Proper error mapping and logging - # @UX_STATE: Error feedback to frontend + # @UX_STATE: Error feedback -> 500 Internal Error raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Task creation failed: {str(e)}" + detail="Internal Task Spawning Error" ) # [/DEF:create_task:Function] -# [/DEF:Shot:FastAPI_Route] +# [/DEF:BackendRouteShot:Module] \ No newline at end of file diff --git a/.ai/shots/critical_module.py b/.ai/shots/critical_module.py new file mode 100644 index 0000000..309f03e --- /dev/null +++ b/.ai/shots/critical_module.py @@ -0,0 +1,79 @@ +# [DEF:TransactionCore:Module] +# @TIER: CRITICAL +# @SEMANTICS: Finance, ACID, Transfer, Ledger +# @PURPOSE: Core banking transaction processor with ACID guarantees. +# @LAYER: Domain (Core) +# @RELATION: DEPENDS_ON -> [DEF:Infra:PostgresDB] +# @RELATION: DEPENDS_ON -> [DEF:Infra:AuditLog] +# @INVARIANT: Total system balance must remain constant (Double-Entry Bookkeeping). +# @INVARIANT: Negative transfers are strictly forbidden. + +# @TEST_DATA: sufficient_funds -> {"from": "acc_A", "to": "acc_B", "amt": 100.00} +# @TEST_DATA: insufficient_funds -> {"from": "acc_empty", "to": "acc_B", "amt": 1000.00} +# @TEST_DATA: concurrency_lock -> {./fixtures/transactions.json#race_condition} + +from decimal import Decimal +from typing import NamedTuple +from ...core.logger import belief_scope +from ...core.db import atomic_transaction, get_balance, update_balance +from ...core.exceptions import BusinessRuleViolation + +class TransferResult(NamedTuple): + tx_id: str + status: str + new_balance: Decimal + +# [DEF:execute_transfer:Function] +# @PURPOSE: Atomically move funds between accounts with audit trails. +# @PARAM: sender_id (str) - Source account. +# @PARAM: receiver_id (str) - Destination account. +# @PARAM: amount (Decimal) - Positive amount to transfer. +# @PRE: amount > 0; sender != receiver; sender_balance >= amount. +# @POST: sender_balance -= amount; receiver_balance += amount; Audit Record Created. +# @SIDE_EFFECT: Database mutation (Rows locked), Audit IO. +# +# @UX_STATE: Success -> Returns 200 OK + Transaction Receipt. +# @UX_STATE: Error(LowBalance) -> 422 Unprocessable -> UI shows "Top-up needed" modal. +# @UX_STATE: Error(System) -> 500 Internal -> UI shows "Retry later" toast. +def execute_transfer(sender_id: str, receiver_id: str, amount: Decimal) -> TransferResult: + # Guard: Input Validation + if amount <= Decimal("0.00"): + raise BusinessRuleViolation("Transfer amount must be positive.") + if sender_id == receiver_id: + raise BusinessRuleViolation("Cannot transfer to self.") + + with belief_scope("execute_transfer") as context: + context.logger.info("Initiating transfer", data={"from": sender_id, "to": receiver_id}) + + try: + # 1. Action: Atomic DB Transaction + # @RELATION: CALLS -> atomic_transaction + with atomic_transaction(): + # Guard: State Validation (Strict) + current_balance = get_balance(sender_id, for_update=True) + + if current_balance < amount: + # @UX_FEEDBACK: Triggers specific UI flow for insufficient funds + context.logger.warn("Insufficient funds", data={"balance": current_balance}) + raise BusinessRuleViolation("INSUFFICIENT_FUNDS") + + # 2. Action: Mutation + new_src_bal = update_balance(sender_id, -amount) + new_dst_bal = update_balance(receiver_id, +amount) + + # 3. Action: Audit + tx_id = context.audit.log_transfer(sender_id, receiver_id, amount) + + context.logger.info("Transfer committed", data={"tx_id": tx_id}) + return TransferResult(tx_id, "COMPLETED", new_src_bal) + + except BusinessRuleViolation as e: + # Logic: Explicit re-raise for UI mapping + raise e + except Exception as e: + # Logic: Catch-all safety net + context.logger.error("Critical Transfer Failure", error=e) + raise RuntimeError("TRANSACTION_ABORTED") from e +# [/DEF:execute_transfer:Function] + +# [/DEF:TransactionCore:Module] \ No newline at end of file diff --git a/.ai/shots/frontend_component.svelte b/.ai/shots/frontend_component.svelte index 05bd165..5309939 100644 --- a/.ai/shots/frontend_component.svelte +++ b/.ai/shots/frontend_component.svelte @@ -1,19 +1,23 @@ - -# @PURPOSE: Reference implementation of a task-spawning component using Constitution rules. -# @RELATION: IMPLEMENTS -> [DEF:Std:UI_Svelte] - + - - - - + \ No newline at end of file diff --git a/.ai/shots/plugin_example.py b/.ai/shots/plugin_example.py index 8c10d8e..28d169a 100644 --- a/.ai/shots/plugin_example.py +++ b/.ai/shots/plugin_example.py @@ -1,6 +1,10 @@ -# [DEF:Shot:Plugin_Example:Example] +# [DEF:PluginExampleShot:Module] +# @TIER: STANDARD +# @SEMANTICS: Plugin, Core, Extension # @PURPOSE: Reference implementation of a plugin following GRACE standards. -# @RELATION: IMPLEMENTS -> [DEF:Std:Plugin] +# @LAYER: Domain (Business Logic) +# @RELATION: INHERITS -> PluginBase +# @INVARIANT: get_schema must return valid JSON Schema. from typing import Dict, Any, Optional from ..core.plugin_base import PluginBase @@ -11,28 +15,15 @@ class ExamplePlugin(PluginBase): def id(self) -> str: return "example-plugin" - @property - def name(self) -> str: - return "Example Plugin" - - @property - def description(self) -> str: - return "A simple plugin that demonstrates structured logging and progress tracking." - - @property - def version(self) -> str: - return "1.0.0" - # [DEF:get_schema:Function] # @PURPOSE: Defines input validation schema. + # @POST: Returns dict compliant with JSON Schema draft 7. def get_schema(self) -> Dict[str, Any]: return { "type": "object", "properties": { "message": { "type": "string", - "title": "Message", - "description": "A message to log.", "default": "Hello, GRACE!", } }, @@ -41,27 +32,33 @@ class ExamplePlugin(PluginBase): # [/DEF:get_schema:Function] # [DEF:execute:Function] - # @PURPOSE: Core plugin logic with structured logging and progress reporting. + # @PURPOSE: Core plugin logic with structured logging and scope isolation. # @PARAM: params (Dict) - Validated input parameters. - # @PARAM: context (TaskContext) - Execution context with logging and progress tools. - async def execute(self, params: Dict[str, Any], context: Optional[TaskContext] = None): - message = params["message"] + # @PARAM: context (TaskContext) - Execution tools (log, progress). + # @SIDE_EFFECT: Emits logs to centralized system. + async def execute(self, params: Dict, context: Optional = None): + message = params - # 1. Action: Structured Logging with Source Attribution - if context: - log = context.logger.with_source("example_plugin") - log.info(f"Starting execution with message: {message}") - - # 2. Action: Progress Reporting - log.progress("Processing step 1...", percent=25) - # Simulating some async work... - # await some_async_op() - - log.progress("Processing step 2...", percent=75) - log.info("Execution completed successfully.") - else: - # Fallback for manual/standalone execution - print(f"Standalone execution: {message}") + # 1. Action: System-level tracing (Rule VI) + with belief_scope("example_plugin_exec") as b_scope: + if context: + # Task Logs: ПишСм Π² ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΉ контСкст выполнСния Π·Π°Π΄Π°Ρ‡ΠΈ + # @RELATION: BINDS_TO -> context.logger + log = context.logger.with_source("example_plugin") + + b_scope.logger.info("Using provided TaskContext") # System log + log.info("Starting execution", data={"msg": message}) # Task log + + # 2. Action: Progress Reporting + log.progress("Processing...", percent=50) + + # 3. Action: Finalize + log.info("Execution completed.") + else: + # Standalone Fallback: ЗамыкаСмся Π½Π° систСмный scope + b_scope.logger.warning("No TaskContext provided. Running standalone.") + b_scope.logger.info("Standalone execution", data={"msg": message}) + print(f"Standalone: {message}") # [/DEF:execute:Function] -# [/DEF:Shot:Plugin_Example] +# [/DEF:PluginExampleShot:Module] \ No newline at end of file