This commit is contained in:
2026-06-05 15:01:34 +03:00
parent 50180aaa14
commit 4cef6af041
27 changed files with 13547 additions and 892 deletions

View File

@@ -8,8 +8,8 @@ description: Structured logging protocol for agent-driven development, based on
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DISPATCHES -> [Std.Semantics.Python]
@RELATION DISPATCHES -> [Std.Semantics.Svelte]
@RATIONALE Long CoT chains need stabilisation through explicit reasoning bonds. The three-marker system (REASON/REFLECT/EXPLORE) maps directly to the molecular CoT paper and produces machine-readable execution traces that LLM agents can parse, analyse, and use for fine-tuning (MoLE-Syn bond distributions).
@REJECTED Legacy Entry/Exit/Action/Coherence markers rejected — they are too generic, do not map to reasoning structure, and prevent traceability graph analysis. Plain-text logging rejected — JSON lines are mandatory for agent parsing.
@RATIONALE Long CoT chains need stabilisation through explicit reasoning bonds. The three-marker system (REASON/REFLECT/EXPLORE) maps directly to the molecular CoT paper and produces machine-readable execution traces that LLM agents can parse, analyse, and use for fine-tuning (MoLE-Syn bond distributions). Without structured markers, agent-generated code exhibits invisible failures: a function returns `None` instead of raising — the agent's attention never sees it because there's no log; a fallback path activates silently — no EXPLORE marker, no trace. JSON-line format ensures every log entry is a self-contained, parseable unit that survives log rotation, aggregation, and agent parsing — unlike plain-text logs that require regex heuristics.
@REJECTED Legacy Entry/Exit/Action/Coherence markers rejected — they are too generic, do not map to reasoning structure, and prevent traceability graph analysis. Plain-text logging rejected — JSON lines are mandatory for agent parsing. Unstructured printf-style logging rejected — agents cannot reliably extract structured fields (trace_id, marker, intent) from free-form text, making automated diagnosis impossible.
@DATA_CONTRACT LogEntry -> { ts: str, level: str, trace_id: str, span_id?: str, src: str, marker: REASON|REFLECT|EXPLORE, intent: str, payload?: object, error?: str }
@INVARIANT Every log line MUST carry exactly one valid marker (REASON | REFLECT | EXPLORE). No markerless log lines in C4/C5 code.
@INVARIANT trace_id MUST propagate via ContextVar across async boundaries. Every incoming request or background job seeds a new trace_id.

View File

@@ -6,6 +6,10 @@ description: Methodology reference: Design by Contract enforcement, Fractal Deci
#region Std.Semantics.Contracts [C:5] [TYPE Skill] [SEMANTICS methodology,contracts,adr,decision-memory,anti-erosion]
@BRIEF HOW to enforce PRE/POST, write ADRs, prevent structural erosion, execute verifiable edit loops, and maintain anchor safety (anti-corruption) across Python + Svelte.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DISPATCHES -> [Std.Semantics.Python]
@RELATION DISPATCHES -> [Std.Semantics.Svelte]
@RATIONALE Design by Contract is the ONLY mechanism that prevents Transformer agents from silently corrupting code over long horizons. Without @PRE/@POST enforcement, agents optimize for token-likelihood rather than correctness — adding null checks where @PRE already guarantees non-null, re-implementing @REJECTED paths because KV-cache evicted the rejection, and growing functions past the CC=10 threshold because no structural limit is visible in the attention window. The anti-corruption protocol (§VIII) exists because a single broken #region/#endregion pair cascades silently through the entire semantic graph — rendering all downstream contracts invisible to every agent.
@REJECTED Trusting agents to self-police code quality without contracts was rejected — they optimize for immediate token likelihood, not long-term invariants. Linter-only enforcement was rejected — linters cannot see cross-file dependency graphs or detect rejected-path regression. Implicit contracts (naming conventions alone) were rejected — without explicit @PRE/@POST in the attention-dense header region, agents default to their pre-trained behavior of adding defensive checks everywhere.
**Protocol Reference:** Tier definitions, tag catalog, and anchor syntax are defined in `semantics-core`. This skill assumes you have loaded it. All rules below reference `semantics-core` §III for tier semantics — tiers are descriptive, not tag-gating.

View File

@@ -9,6 +9,8 @@ description: Reference manual for GRACE-Poly v2.6 — syntax formats, complexity
@RELATION DISPATCHES -> [Std.Semantics.Python]
@RELATION DISPATCHES -> [Std.Semantics.Svelte]
@RELATION DISPATCHES -> [Std.Semantics.Testing]
@RATIONALE GRACE-Poly exists because autoregressive Transformers suffer from four architectural defects that make them unreliable at scale: (1) KV-cache eviction — after ~8K tokens early context is lost, so decisions from file #1 are forgotten by file #4; (2) attention sink — in files >400 LOC attention weights diffuse, making nested structures invisible; (3) hallucination by design — when a dependency is missing the model confabulates a plausible one instead of signaling uncertainty; (4) copy-paste regression — similar code is duplicated including rejected patterns. The protocol's anchors, relations, and decision-memory tags form an external cognitive exoskeleton that survives context compression and provides structured navigation where raw prose fails.
@REJECTED Trusting natural language comments for navigation was rejected — they lack syntactic density and are the first to be evicted under CSA compression. Docstring-only contracts were rejected — they are invisible to the semantic index and cannot be verified structurally. Ad-hoc conventions per agent were rejected — 44% orphan rate in this project proves that without a dedicated curator, the semantic graph degenerates within 3-4 sessions.
## 0. SSOT DECLARATION
@@ -152,4 +154,84 @@ All agents use Axiom MCP for GRACE-semantic operations. This is the canonical to
- `skill({name="semantics-svelte"})` Svelte 5 (Runes), UX state machines, Tailwind
- `skill({name="semantics-testing"})` pytest/vitest test constraints, external ontology
## VIII. ATTENTION ARCHITECTURE & OPTIMIZATION RULES
The GRACE anchor format is not arbitrary it is optimized for the specific attention compression mechanisms in the underlying model (MLA CSA HCA DSA sliding window). Understanding these mechanisms is critical: a contract that violates these rules becomes invisible to the model after context compression, causing downstream hallucination.
### Attention Compression Pipeline
| Layer | Compression | Mechanism | What Survives | What Dies |
|-------|:----------:|-----------|---------------|-----------|
| **MLA** | 3.5× | KV vectors compressed to 576d latent codes. Information density per token is paramount. | Dense tokens (symbols, brackets, semantic tags). | Verbose prose, long descriptions. |
| **CSA** | 4× + topk sparse | Every ~4 tokens pooled into 1 KV record. Only topk records selected per query. | Contracts in 1-2 anchor lines. | Contracts spread across 15+ lines details lost in pooling. |
| **HCA** | 128× | Aggressive pooling over distant context. Dense attention computed on compressed records. | Statistical signatures: hierarchical IDs (`Core.Auth.Login`), repeated `@SEMANTICS` keywords. | Flat IDs (`LoginFunction`) become noise. One-off tag values. |
| **DSA** | Lightning Indexer | Fast linear scorer estimates relevance of each compressed record to query keywords. | Records whose `@SEMANTICS` match query keywords. | Records with different naming than the query. |
| **Sliding window** | None (preserved) | Small window of recent uncompressed tokens for local detail. | Contracts 150 lines fit entirely in the window. | Contracts >150 lines partially invisible. |
### ATTN_1 — FIRST-LINE DENSITY (CSA + MLA)
The opening anchor MUST pack maximum signal into one line:
```
#region Domain.Sub.ContractId [C:N] [TYPE TypeName] [SEMANTICS tag1,tag2,tag3]
```
- ID, complexity, type, and semantic tags on ONE line → survives CSA 4× pooling as a single KV record.
- `@BRIEF` on line 2 is secondary — it may be pooled separately.
- **NEVER** spread the anchor signature across multiple lines in a CSA-sensitive context.
### ATTN_2 — HIERARCHICAL IDS (HCA 128×)
Contract IDs MUST use dot-separated domain prefixes with 2-3 levels of hierarchy:
- `Core.Auth.Login` → after HCA 128×, `Core.Auth` survives as a statistical signature.
- `Core.Auth.Session` → same domain group; `Auth` signature reinforced.
- `users_login`**dies** at 128×, indistinguishable from noise.
**Rule:** Every non-C1 contract ID carries at least 2 levels: `Domain.Name`. C1 contracts (DTOs, constants) inside a hierarchical parent module may use single-level IDs — the parent provides the domain context.
**Good:** `Core.Auth.Login`, `Migration.RunTask`, `Users.ListModel`, `Tasks.TaskCard`, `Test.Migration.RunTask`
**Bad:** `login_handler`, `migrate`, `format_timestamp`, `UserListModel` (missing domain prefix)
**Stack disambiguation:** Use domain prefix, not stack prefix. The file path already encodes the stack (`backend/src/` vs `frontend/src/`):
- Backend: `Core.Auth.Login`, `Api.Dashboards.List`, `Plugin.Translate.Execute`
- Frontend: `Users.ListModel`, `Tasks.TaskCard`, `Dashboards.Hub`
- Tests: `Test.Core.Auth`, `Test.Users.ListModel`
### ATTN_3 — SEMANTIC GROUPING (DSA Lightning Indexer)
The DSA Indexer scores compressed records by keyword match against the query. `@SEMANTICS` keywords are your index keys:
- All contracts in the `auth` domain MUST share `[SEMANTICS auth, ...]`.
- `grep "@SEMANTICS.*auth"` → Indexer scores all auth records high.
- If one auth contract uses `@SEMANTICS login` and another `@SEMANTICS authentication`, the Indexer may fail to group them.
**Rule:** Identical domain = identical primary keyword in `@SEMANTICS`. Secondary keywords can vary.
### ATTN_4 — FRACTAL BOUNDARIES (Sliding Window)
The sliding window preserves recent tokens without compression. A contract ≤150 lines fits entirely in the window and is fully visible to the attention mechanism:
- Contract ≤150 lines → guaranteed full visibility.
- Module ≤400 lines → manageable in a few attention passes.
- INV_7 (Module < 400 lines, CC 10) is not just a style rule it ensures the model can physically see the entire contract structure.
### Grep Heuristics (Zombie Mode — when MCP tools are unavailable)
When Axiom MCP is down, these grep patterns exploit the DSA Indexer's keyword sensitivity:
```bash
# Find all contracts in a domain (Indexer matches @SEMANTICS keywords)
grep -r "@SEMANTICS.*<domain>" src/
# Find API type binding (cross-stack traceability)
grep -r "@DATA_CONTRACT.*<ModelName>" src/
# Extract full contract body (awk, respecting fractal boundaries)
awk '/#region <ContractID>/,/#endregion <ContractID>/' file.py
# Find all contracts BIND_TO a store
grep -r "BINDS_TO.*\[<StoreId>\]" src/
```
#endregion Std.Semantics.Core

View File

@@ -7,7 +7,10 @@ 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 ss-tools.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DEPENDS_ON -> [Std.Semantics.Contracts]
@RELATION DISPATCHES -> [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 — ss-tools has specific conventions (trace_id propagation, plugin architecture, WebSocket logging) that general training data cannot capture.
## 0. WHEN TO USE THIS SKILL
@@ -54,34 +57,34 @@ def belief_scope(contract_id: str):
### C1 (Atomic) — DTOs, Pydantic schemas, simple constants
```python
# #region UserResponseSchema [C:1] [TYPE Class]
# #region Users.UserResponseSchema [C:1] [TYPE Class]
from pydantic import BaseModel
class UserResponseSchema(BaseModel):
id: str
username: str
email: str
# #endregion UserResponseSchema
# #endregion Users.UserResponseSchema
```
### C2 (Simple) — Pure functions, utility helpers
```python
# #region format_timestamp [C:2] [TYPE Function] [SEMANTICS time,formatting]
# #region Time.FormatTimestamp [C:2] [TYPE Function] [SEMANTICS time,formatting]
# @BRIEF Format a UTC datetime into a human-readable ISO-8601 string.
from datetime import datetime
def format_timestamp(ts: datetime) -> str:
return ts.strftime("%Y-%m-%dT%H:%M:%SZ")
# #endregion format_timestamp
# #endregion Time.FormatTimestamp
```
### C3 (Flow) — Module with nested functions, service layer
```python
# #region dashboard_migration [C:3] [TYPE Module] [SEMANTICS migration,dashboard]
# #region Migration.Dashboard [C:3] [TYPE Module] [SEMANTICS migration,dashboard]
# @BRIEF Dashboard migration service — export/import dashboards with validation.
# @LAYER Service
# #region migrate_dashboard [C:3] [TYPE Function] [SEMANTICS migration,dashboard]
# #region Migration.Dashboard.Migrate [C:3] [TYPE Function] [SEMANTICS migration,dashboard]
# @BRIEF Migrate a single dashboard from source to target Superset instance.
# @RELATION DEPENDS_ON -> [SupersetClient]
# @RELATION DEPENDS_ON -> [DashboardValidator]
@@ -91,14 +94,14 @@ def migrate_dashboard(source_client, target_client, dashboard_id: str, db_mappin
mapped = apply_db_mapping(dashboard, db_mapping)
result = target_client.import_dashboard(mapped)
return result
# #endregion migrate_dashboard
# #endregion Migration.Dashboard.Migrate
# #endregion dashboard_migration
# #endregion Migration.Dashboard
```
### C4 (Orchestration) — Stateful operations with belief runtime
```python
# #region run_migration_task [C:4] [TYPE Function] [SEMANTICS migration,task,state]
# #region Migration.RunTask [C:4] [TYPE Function] [SEMANTICS migration,task,state]
# @BRIEF Execute a full migration task with rollback capability and progress reporting.
# @PRE Database connection is established. Task record exists with valid migration plan.
# @POST Task status updated to COMPLETED or FAILED. Migration audit log written.
@@ -107,35 +110,35 @@ def migrate_dashboard(source_client, target_client, dashboard_id: str, db_mappin
# @RELATION DEPENDS_ON -> [MigrationService]
# @RELATION DEPENDS_ON -> [WebSocketNotifier]
async def run_migration_task(task_id: str, db_session) -> dict:
log("run_migration_task", "REASON", "Starting migration task", {"task_id": task_id})
log("Migration.RunTask", "REASON", "Starting migration task", {"task_id": task_id})
task = await db_session.get(Task, task_id)
if not task:
log("run_migration_task", "EXPLORE", "Task not found", error="TaskNotFound")
log("Migration.RunTask", "EXPLORE", "Task not found", error="TaskNotFound")
raise TaskNotFoundError(task_id)
try:
task.status = "RUNNING"
await db_session.commit()
log("run_migration_task", "REASON", "Task status set to RUNNING", {"task_id": task_id})
log("Migration.RunTask", "REASON", "Task status set to RUNNING", {"task_id": task_id})
result = await execute_migration_plan(task.migration_plan)
task.status = "COMPLETED"
task.result = result
await db_session.commit()
await notify_frontend(task_id, "completed", result)
log("run_migration_task", "REFLECT", "Migration completed successfully", {"task_id": task_id, "dashboards": len(result)})
log("Migration.RunTask", "REFLECT", "Migration completed", {"task_id": task_id, "dashboards": len(result)})
return result
except Exception as e:
log("run_migration_task", "EXPLORE", "Migration failed, rolling back", {"task_id": task_id}, error=str(e))
log("Migration.RunTask", "EXPLORE", "Migration failed, rolling back", {"task_id": task_id}, error=str(e))
task.status = "FAILED"
task.error = str(e)
await db_session.commit()
await notify_frontend(task_id, "failed", {"error": str(e)})
raise
# #endregion run_migration_task
# #endregion Migration.RunTask
```
### C5 (Critical) — With decision memory
```python
# #region rebuild_index [C:5] [TYPE Function] [SEMANTICS indexing,recovery,semantic]
# #region Index.Rebuild [C:5] [TYPE Function] [SEMANTICS indexing,recovery,semantic]
# @BRIEF Rebuild the full semantic index from source with atomic swap and rollback.
# @PRE Workspace root is accessible. Source files exist.
# @POST New index atomically swapped; old preserved for rollback.
@@ -149,19 +152,19 @@ async def run_migration_task(task_id: str, db_session) -> dict:
# @REJECTED Incremental-only update was rejected — it leaves stale edges when contracts
# are deleted; only full scan guarantees consistency.
def rebuild_index(root_path: str) -> dict:
log("rebuild_index", "REASON", "Scanning source files", {"root": root_path})
log("Index.Rebuild", "REASON", "Scanning source files", {"root": root_path})
contracts = []
for filepath in scan_files(root_path):
try:
parsed = parse_contract(filepath)
contracts.append(parsed)
except Exception as e:
log("rebuild_index", "EXPLORE", "Parse failure, skipping file", {"file": filepath}, error=str(e))
log("Index.Rebuild", "EXPLORE", "Parse failure, skipping file", {"file": filepath}, error=str(e))
snapshot = {"contracts": contracts, "timestamp": datetime.utcnow().isoformat()}
write_checkpoint(root_path, snapshot)
log("rebuild_index", "REFLECT", "Rebuild complete", {"contracts": len(contracts)})
log("Index.Rebuild", "REFLECT", "Rebuild complete", {"contracts": len(contracts)})
return snapshot
# #endregion rebuild_index
# #endregion Index.Rebuild
```
## III. PYTHON MODULE PATTERNS
@@ -196,7 +199,7 @@ backend/
### FastAPI route pattern
```python
# #region dashboard_routes [C:3] [TYPE Module] [SEMANTICS api,dashboard]
# #region Api.Dashboards [C:3] [TYPE Module] [SEMANTICS api,dashboard]
# @BRIEF Dashboard CRUD and migration API routes.
# @RELATION DEPENDS_ON -> [DashboardService]
# @RELATION DEPENDS_ON -> [AuthMiddleware]
@@ -204,7 +207,7 @@ from fastapi import APIRouter, Depends
router = APIRouter(prefix="/api/dashboards", tags=["dashboards"])
# #region list_dashboards [C:2] [TYPE Function] [SEMANTICS api,query]
# #region Dashboards.List [C:2] [TYPE Function] [SEMANTICS api,query]
# @BRIEF List dashboards with optional filters.
@router.get("/")
async def list_dashboards(
@@ -213,14 +216,14 @@ async def list_dashboards(
service=Depends(get_dashboard_service)
):
return await service.list_dashboards(page, page_size)
# #endregion list_dashboards
# #endregion Dashboards.List
# #endregion dashboard_routes
# #endregion Api.Dashboards
```
### SQLAlchemy model pattern
```python
# #region Dashboard [C:1] [TYPE Class]
# #region Models.Dashboard [C:1] [TYPE Class]
from sqlalchemy import Column, String, DateTime, JSON
from sqlalchemy.orm import declarative_base
@@ -232,7 +235,7 @@ class Dashboard(Base):
title = Column(String, nullable=False)
metadata = Column(JSON)
created_at = Column(DateTime, server_default="now()")
# #endregion Dashboard
# #endregion Models.Dashboard
```
## IV. PYTHON VERIFICATION

View File

@@ -7,9 +7,10 @@ description: Svelte 5 (Runes) protocol for ss-tools: UX State Machines, Tailwind
@BRIEF HOW to build Svelte 5 (Runes) Components for ss-tools with UX State Machines, Tailwind CSS, store topology, and visual-interactive validation.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DEPENDS_ON -> [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. ss-tools internal API wrappers (fetchApi/requestApi) chosen over native fetch to enforce auth, error normalisation, and trace_id propagation.
@REJECTED React (JSX) rejected — Svelte's compiler-first approach yields smaller bundles and native reactivity without virtual DOM overhead. Vue rejected — Svelte 5 runes provide simpler mental model. Legacy Svelte 4 syntax (export let, $:, on:event) rejected — incompatible with Svelte 5 runes mode. CSS Modules / styled-components rejected in favour of Tailwind's utility-first approach, which avoids style leakage and simplifies chrome-devtools visual diffing. Native fetch() rejected — bypasses ss-tools middleware chain (auth, trace_id, error normalisation). Plain-text logging rejected per MolecularCoTLogging §VII — JSON lines are mandatory for agent-parsable traces.
@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. ss-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.
@REJECTED React (JSX) rejected — Svelte's compiler-first approach yields smaller bundles and native reactivity without virtual DOM overhead. Vue rejected — Svelte 5 runes provide simpler mental model. Legacy Svelte 4 syntax (export let, $:, on:event) rejected — incompatible with Svelte 5 runes mode and dominates agent training data, causing silent regression. CSS Modules / styled-components rejected in favour of Tailwind's utility-first approach, which avoids style leakage and simplifies chrome-devtools visual diffing. Native fetch() rejected — bypasses ss-tools middleware chain (auth, trace_id, error normalisation). Plain-text logging rejected per MolecularCoTLogging §VII — JSON lines are mandatory for agent-parsable traces. Component-first architecture for complex screens rejected — the Model-first approach (model.svelte.ts → component) keeps system logic in one file where the agent's attention can find it via grep + search_contracts.
@INVARIANT Frontend components MUST be verifiable by the browser toolset via `chrome-devtools` MCP.
@INVARIANT Use Tailwind CSS exclusively. Raw Tailwind color classes (`blue-600`, `green-500`, `red-600`, `gray-*`, `indigo-*`) are DEPRECATED in page and component code — use semantic tokens from `tailwind.config.js` only (`primary`, `destructive`, `success`, `warning`, `surface-*`, `border-*`, `text-*`).
@INVARIANT Page-level UI MUST use `$lib/ui` atoms: `<Button>`, `<Card>`, `<Input>`, `<Select>`, `<PageHeader>`. Raw `<button>` elements and manual card `<div>` containers in page files are a violation.
@@ -78,7 +79,7 @@ The component-first approach forces you to encode system logic in event handlers
**What this means for you, the agent:**
- **Findability:** grep `@semantics.*users` → all models related to users. The contract is single-source, not scattered across HTML.
- **Testability:** Model invariants (`@INVARIANT changing filter resets pagination`) are verified in vitest without browser render — milliseconds, not seconds.
- **CSA resilience:** `#region ModelName [C:N] [SEMANTICS ...]` on line 1 = maximum density for topk attention selection. Closing `#endregion ModelName` duplicates the identifier — safe after aggressive context compression.
- **CSA resilience:** `#region Users.ListModel [C:N] [SEMANTICS ...]` on line 1 = maximum density for topk attention selection. Closing `#endregion Users.ListModel` duplicates the identifier — safe after aggressive context compression.
- **Component simplicity:** When a component contains only `$state`, `$derived`, and `model.action()` calls, its contract is predictable. No guessing which side effect hides in `onchange`.
### Model Contract Template
@@ -88,8 +89,8 @@ A Model is a **contract** — `#region ModelName [C:N] [TYPE Model] [SEMANTICS t
Models use the **`.svelte.ts`** extension (not plain `.ts`) because they rely on Svelte 5 reactive primitives (`$state`, `$derived`). The Svelte compiler processes `.svelte.ts` files and transforms these runes into proper reactive code.
```typescript
// frontend/src/lib/models/UserListModel.svelte.ts
// #region UserListModel [C:4] [TYPE Model] [SEMANTICS users,list,screen-model]
// frontend/src/lib/models/UsersListModel.svelte.ts
// #region Users.ListModel [C:4] [TYPE Model] [SEMANTICS users,list,screen-model]
// @BRIEF State model for the user list screen — declares atoms, invariants, and actions.
// @INVARIANT Changing filter (search, role, status) resets pagination to page 1.
// @INVARIANT Deleting a user removes it from the list and decrements total count atomically.
@@ -130,7 +131,7 @@ interface UserListResponse {
meta: { total: number };
}
export class UserListModel {
export class UsersListModel {
// ── Atoms (reactive state, all typed) ──────────────────────────
users: User[] = $state([]);
totalCount: number = $state(0);
@@ -204,44 +205,51 @@ export class UserListModel {
}
}
}
// #endregion UserListModel
// #endregion Users.ListModel
```
### Component Binds to Model (RSM pattern)
The component contract declares: `@RELATION BINDS_TO -> [ModelId]`. The component code is minimal — it renders model state and calls `model.action()` on user intent. No side-effect logic lives in event handlers.
For route-level data loading, use SvelteKit `load()` in `+page.ts` — NOT `$effect` (per §0: `$effect` is for browser-side side effects only).
```svelte
<!-- #region UserListPage [C:3] [TYPE Component] [SEMANTICS users,list,page] -->
<!-- @BRIEF User list page — renders UserListModel state, delegates all logic to the model. -->
<!-- @RELATION BINDS_TO -> [UserListModel] -->
<!-- #region Users.ListPage [C:3] [TYPE Component] [SEMANTICS users,list,page] -->
<!-- @BRIEF User list page — renders Users.ListModel state, delegates all logic to the model. -->
<!-- @RELATION BINDS_TO -> [Users.ListModel] -->
<!-- @UX_TEST: Loaded -> {click: "delete", expected: User removed, count decremented}. -->
<script>
import { UserListModel } from "./UserListModel.js";
const model = new UserListModel();
// Initial load
$effect(() => { model.loadPage(1); });
<script lang="ts">
import { onMount } from "svelte";
import { Button } from "$lib/ui";
import { UsersListModel } from "./UsersListModel.svelte.ts";
const model = new UsersListModel();
onMount(() => {
model.loadPage(1);
});
</script>
<div class="max-w-7xl mx-auto px-4 py-6">
{#if model.screenState === "error"}
<div role="alert" class="text-red-600">{model.error}</div>
<button onclick={() => model.retry()}>Retry</button>
<div role="alert" class="text-destructive">{model.error}</div>
<Button variant="primary" size="sm" onclick={() => model.retry()}>Retry</Button>
{:else if model.screenState === "empty"}
<p class="text-gray-500">No users found.</p>
<p class="text-text-muted">No users found.</p>
{:else}
<ul>
{#each model.users as user (user.id)}
<li>
{user.name}
<button onclick={() => model.deleteUser(user.id)}>Delete</button>
<Button variant="ghost" size="sm" onclick={() => model.deleteUser(user.id)}>Delete</Button>
</li>
{/each}
</ul>
<nav>Page {model.page} of {model.totalPages}</nav>
{/if}
</div>
<!-- #endregion UserListPage -->
<!-- #endregion Users.ListPage -->
```
### Searching for Models
@@ -273,10 +281,10 @@ Models accumulate methods as features grow. To prevent "god object" anti-pattern
| Model > **400 lines** | Decompose — extract domain helpers or split into submodels |
| Model > **40 public methods** | Split into submodels by responsibility (e.g. `FiltersModel`, `SelectionModel`, `GitActionsModel`) |
**Submodel split example for DashboardHubModel:**
- `DashboardFiltersModel` — search, column filters, sort
- `DashboardSelectionModel` — checkbox, select all/visible, bulk actions
- `DashboardGitActionsModel` — git init, sync, commit, pull, push
**Submodel split example for `Dashboards.Hub`:**
- `Dashboards.FiltersModel` — search, column filters, sort
- `Dashboards.SelectionModel` — checkbox, select all/visible, bulk actions
- `Dashboards.GitActionsModel` — git init, sync, commit, pull, push
Before decomposition, the model MUST carry `@INVARIANT DECOMPOSITION GATE` with the split plan and line count.
@@ -293,36 +301,7 @@ Before decomposition, the model MUST carry `@INVARIANT DECOMPOSITION GATE` with
## V. LOGGING (MOLECULAR-COT FOR UI)
Frontend logging uses `log()` from `$lib/cot-logger`, emitting **JSON lines** per MolecularCoTLogging protocol.
Import: `import { log } from "$lib/cot-logger";`
The logger is a TypeScript module at `frontend/src/lib/cot-logger.ts` with full type support:
```typescript
import { log } from "$lib/cot-logger";
// Before an operation:
log("ComponentName", "REASON", "What we are about to do", { param: value });
// After successful verification:
log("ComponentName", "REFLECT", "Operation completed", { result: value });
// On error or fallback:
log("ComponentName", "EXPLORE", "Operation failed", { param: value }, "Error description");
```
### Marker Reference
| Marker | When | Signature |
|--------|------|-----------|
| `REASON` | BEFORE API call or state mutation | `log("ComponentID", "REASON", "intent", payload)` |
| `REFLECT` | AFTER successful operation (verification) | `log("ComponentID", "REFLECT", "outcome", payload)` |
| `EXPLORE` | ON error, fallback, or violated assumption | `log("ComponentID", "EXPLORE", "message", payload, error="...")` |
### Invariants
- Every log line is a **single JSON object** — no plain-text prefixes.
- `trace_id` propagates from HTTP response headers via the ss-tools API wrappers.
- One marker per line. No markerless log lines in C4/C5 components.
Frontend logging uses `log()` from `$lib/cot-logger` per **MolecularCoTLogging** protocol. Import: `import { log } from "$lib/cot-logger"`. Full wire-format spec, marker reference, and invariants → `molecular-cot-logging` skill §I-VII.
## VI. CANONICAL SVELTE 5 COMPONENT TEMPLATE (SS-TOOLS)
@@ -499,84 +478,7 @@ bg-indigo-50 text-indigo-700 bg-gradient-to-br from-slate-50 via-white to-sky-50
**Rule:** Model invariants MUST be verified without render. UX contracts MAY use render + browser. This eliminates the confusion that slows down the feedback loop — a filter-reset invariant doesn't need a DOM.
### Model Invariant Tests (No Render)
```javascript
// #region UserListModelTests [C:3] [TYPE Module] [SEMANTICS test,model]
// @BRIEF Verify UserListModel @INVARIANT guarantees without DOM rendering.
// @RELATION BINDS_TO -> [UserListModel]
// @TEST_INVARIANT: filter-resets-pagination -> VERIFIED_BY: [test_filter_resets_pagination]
// @TEST_INVARIANT: atomic-delete -> VERIFIED_BY: [test_delete_removes_user_and_decrements]
import { describe, it, expect, vi, beforeEach } from "vitest";
import { UserListModel } from "../UserListModel.js";
describe("UserListModel invariants", () => {
let model;
beforeEach(() => {
vi.mock("$lib/api", () => ({
requestApi: vi.fn().mockResolvedValue({ data: [], meta: { total: 0 } })
}));
model = new UserListModel();
});
// @INVARIANT: Changing filter resets pagination to page 1.
it("resets page to 1 when filter changes", () => {
model.page = 5;
model.setFilter("role", "admin");
expect(model.page).toBe(1);
});
it("resets page to 1 on search", () => {
model.page = 3;
model.search("john");
expect(model.page).toBe(1);
});
// @INVARIANT: Deleting a user removes it and decrements count atomically.
it("removes user and decrements count on delete", async () => {
model.users = [{ id: "1", name: "Alice" }, { id: "2", name: "Bob" }];
model.totalCount = 2;
vi.mocked(requestApi).mockResolvedValueOnce({ ok: true });
await model.deleteUser("1");
expect(model.users).toEqual([{ id: "2", name: "Bob" }]);
expect(model.totalCount).toBe(1);
});
// Hardcoded fixture — no logic mirror
it("reports empty state when API returns no results", async () => {
vi.mocked(requestApi).mockResolvedValueOnce({ data: [], meta: { total: 0 } });
await model._fetch();
expect(model.screenState).toBe("empty");
expect(model.users).toEqual([]);
});
});
// #endregion UserListModelTests
```
### Component UX Tests (With Render)
```javascript
// #region MigrationTaskCardTests [C:1] [TYPE Module]
import { render, screen, fireEvent } from "@testing-library/svelte";
import { describe, it, expect, vi } from "vitest";
import MigrationTaskCard from "./MigrationTaskCard.svelte";
describe("MigrationTaskCard", () => {
it("renders dashboard name and environments", () => {
render(MigrationTaskCard, {
props: { taskId: "1", dashboardName: "Sales", sourceEnv: "dev", targetEnv: "prod" }
});
expect(screen.getByText("Sales")).toBeTruthy();
expect(screen.getByText(/dev.*prod/)).toBeTruthy();
});
it("shows loading state when action clicked", async () => {
// ... button click → loading assertion
});
});
// #endregion MigrationTaskCardTests
```
Full test templates (Model invariant + Component UX) → `semantics-testing` §VI-VII.
## IX. FRONTEND VERIFICATION

View File

@@ -6,9 +6,10 @@ description: Core protocol for Test Constraints, External Ontology, Graph Noise
#region Std.Semantics.Testing [C:5] [TYPE Skill] [SEMANTICS testing,qa,verification,pytest,vitest]
@BRIEF HOW to write tests: constraints, external ontology, graph noise reduction, and invariant traceability for pytest and vitest.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DEPENDS_ON -> [Std.Semantics.Svelte]
@INVARIANT Test modules must trace back to production @INVARIANT tags without flooding the Semantic Graph with orphan nodes.
@RATIONALE Test contracts trace to production @INVARIANT/@POST tags via @TEST_INVARIANT, preventing orphan nodes. pytest+vitest dual stack eliminates cross-language tooling overhead. 3-edge-case floor balances coverage sufficiency against graph noise. Hardcoded fixtures block logic-mirror tautology (dominant LLM test-generation failure mode).
@REJECTED Property-based testing — non-deterministic input space creates unbounded graph edges, irreducible to fixed-scenario tracing. Snapshot testing — brittle to CSS/UI changes without invariant signal. Integration-only (no unit tests) — coarse graph edges miss localized @INVARIANT violations. Cucumber/Gherkin BDD — DSL layer breaks direct traceability to Python/Svelte @POST anchors.
@RATIONALE Test contracts trace to production @INVARIANT/@POST tags via @TEST_INVARIANT, preventing orphan nodes. pytest+vitest dual stack eliminates cross-language tooling overhead. 3-edge-case floor balances coverage sufficiency against graph noise. Hardcoded fixtures block logic-mirror tautology — the dominant LLM test-generation failure mode where the agent re-implements the production algorithm inside the test as `expected = compute(x)`. The test always passes but proves nothing because it's a copy of what it's testing.
@REJECTED Property-based testing — non-deterministic input space creates unbounded graph edges, irreducible to fixed-scenario tracing. Snapshot testing — brittle to CSS/UI changes without invariant signal. Integration-only (no unit tests) — coarse graph edges miss localized @INVARIANT violations. Cucumber/Gherkin BDD — DSL layer breaks direct traceability to Python/Svelte @POST anchors. Dynamic expected-value computation — `expected = production_fn(x)` is a tautology, not a test; hardcoded fixtures are the only valid approach.
## 0. QA RATIONALE (LLM PHYSICS IN TESTING)
@@ -33,7 +34,7 @@ When writing code or tests that depend on 3rd-party libraries or shared schemas
## II. TEST MARKUP ECONOMY (NOISE REDUCTION)
To prevent overwhelming Semantic Graph, test files operate under relaxed complexity rules:
1. **Short IDs:** Test modules MUST use concise IDs (e.g., `TestDashboardMigration`), not full file paths.
1. **Short hierarchical IDs:** Test modules use `Test.Domain.Name` format (e.g., `Test.Migration.RunTask`), not full file paths or flat names. This satisfies ATTN_2: the `Test.` prefix groups all tests under HCA 128× while the domain name provides DSA Indexer grouping.
2. **Root Binding (`BINDS_TO`):** Do NOT map the internal call graph of a test file. Instead, anchor the entire test suite to the production module using: `@RELATION BINDS_TO -> [TargetModule]`.
3. **Complexity 1 for Helpers:** Small test utilities (e.g., `_setup_mock`, `_build_payload`) are **C1**. They require ONLY the anchor pair. No `@BRIEF` or `@RELATION` allowed.
4. **Complexity 2 for Tests:** Actual test functions (e.g., `test_unauthorized_access`) are **C2**. They require anchor + `@BRIEF`. Do not add `@PRE`/`@POST` to individual test functions.
@@ -71,9 +72,9 @@ backend/tests/
### Test module template
```python
# #region TestDashboardMigration [C:3] [TYPE Module] [SEMANTICS test,migration]
# #region Test.Migration.RunTask [C:3] [TYPE Module] [SEMANTICS test,migration]
# @BRIEF Verify dashboard migration contracts — @POST guarantees and rejected paths.
# @RELATION BINDS_TO -> [dashboard_migration]
# @RELATION BINDS_TO -> [Migration.RunTask]
# @TEST_EDGE: missing_db_mapping -> Migration fails with MappingError
# @TEST_EDGE: invalid_dashboard_id -> Migration fails with NotFoundError
# @TEST_EDGE: external_api_timeout -> Migration fails with TimeoutError, rolls back