# Valstorm Business Operating System (BOS) Full Documentation Corpus --- ## Tools - Provide the capabilities to leverage your computer, use the web, interact with media, and access your files. ## Memory - Provides a short-term context for AI to recall between different chat sessions. ## Skills - Provides detailed step by step instructions for AI to follow to complete a task. These turn your workflows into repeatable processes. ## Context - Files that provide instructions, conventions, SOPs, and other information that is useful for AI to understand your company and how it works. This is the long-term memory of your company. ## Profiles - Profiles are the combinition of Tools, memory, skills and context that make an agent exceptionally good a specific number of tasks. A marketer profile will have a different set of tools, memory, skills and context than a software engineer profile. - Profiles are an exceptional way to delegate work. The orchestrator pattern allows for a single agent to take your request and break it down to sub-tasks that can be delegated to specialized agents. This allows for a single agent to manage a complex task that requires multiple skill sets. ## Prompting Best Practices - Provide clear instructions for AI to follow. - Provide as much additional context as possible. Where are the files it needs or how to access them? What is the goal of the task? What is the desired result? What are the constraints? What is the format of the output? - If you find yourself repeating instructions, create a skill that can be reused to help load context. - Create pr --- ## 1. Executive Summary & Model Tier Taxonomy Modern LLMs fall into three distinct operational tiers based on reasoning depth, latency, token economics, and tool execution reliability: ``` ┌────────────────────────────────────────────────────────────────────────┐ │ TIER 1: HEAVY THINKERS │ │ System Architecture • Technical Planning • High-Stakes Ideation │ │ Gemini 3.1 Pro Preview | Claude Opus 5 | gpt-5.6-sol │ └───────────────────────────────────┬────────────────────────────────────┘ │ Generates Architecture & Specs ▼ ┌────────────────────────────────────────────────────────────────────────┐ │ TIER 2: REASONERS │ │ Orchestration • Monorepo Dev • Tool Calling • Pytest & Playwright │ │ Gemini 3.7 / 3.6 Flash | Claude Sonnet 5 | gpt-5.3-codex │ └───────────────────────────────────┬────────────────────────────────────┘ │ Delegates Micro-Tasks ▼ ┌────────────────────────────────────────────────────────────────────────┐ │ TIER 3: WORKER BEES │ │ Spec Execution • CUD Mutations • Schema Lookups • Data Scraping │ │ Gemini 3.5 / 3.1 Flash-Lite | Claude Haiku 4.5 | gpt-5.6-luna │ └────────────────────────────────────────────────────────────────────────┘ ``` --- ## 2. Model Tier Specification & Best Use Cases ### Tier 1: Heavy Thinkers (Architects & High-Stakes Planners) * **Role**: High-level system architecture, multi-file code planning, complex database schema design, technical trade-off evaluation, and deep root-cause debugging breakdowns. * **When to Use**: Only invoke during the planning/architecture phase or when an escalation threshold is reached (e.g. when a sub-agent is stuck). * **Cost Range**: $2.00 - $5.00+ / 1M input tokens | $12.00 - $30.00+ / 1M output tokens. * **Recommended Models**: - **Google Gemini**: `gemini-3.1-pro-preview` ($2.00 input / $12.00 output) — Massive context window, superior structural reasoning. - **Anthropic Claude**: `claude-opus-5` ($5.00 input / $25.00 output) — Unmatched nuanced comprehension and architectural prose. - **OpenAI**: `gpt-5.6-sol` ($5.00 input / $30.00 output) / `gpt-5.5` ($5.00 / $30.00) — Deep chain-of-thought and mathematical rigor. --- ### Tier 2: Reasoners (Orchestrators, Full-Stack Devs & QA Engineers) * **Role**: Interactive REPL orchestration, multi-tool agent execution, monorepo coding (FastAPI backend + React frontend), test suite authoring (Pytest/Playwright), API integration, domain research, and dynamic debugging. * **When to Use**: The primary engine for all active interactive turns, multi-tool workflows, and specialized developers. They reason reliably, make structured tool calls without schema hallucination, and run at low latency (< 1.5s TTFT). * **Cost Range**: $1.50 - $3.00 / 1M input tokens | $7.50 - $15.00 / 1M output tokens. * **Recommended Models**: - **Google Gemini**: `gemini-3.7-flash` / `gemini-3.6-flash` ($1.50 input / $7.50 output) — Industry-leading speed-to-intelligence ratio, excellent function calling. - **Anthropic Claude**: `claude-sonnet-5` ($2.00 input / $10.00 output) — Exceptional code generation, precise refactoring, and tool execution. - **OpenAI**: `gpt-5.6-terra` ($2.00 input / $12.00 output) / `gpt-5.3-codex` ($1.75 input / $14.00 output) — Specialized code completions and fast reasoning. --- ### Tier 3: Worker Bees (Spec Executors & Single-Task Micro-Agents) * **Role**: Pure execution of rigid specifications, single-field CRUD mutations, schema lookups, basic SQL generation, log aggregation, web scraping extraction, simple data formatting, and summarization. * **When to Use**: High-throughput automated pipelines and isolated sub-tasks where the prompt already provides exact instructions and constraints. * **Cost Range**: $0.20 - $1.00 / 1M input tokens | $1.20 - $5.00 / 1M output tokens. * **Recommended Models**: - **Google Gemini**: `gemini-3.5-flash-lite` ($0.30 input / $2.50 output) / `gemini-3.1-flash-lite` ($0.25 input / $1.50 output) — High-throughput, ultra-low cost. - **Anthropic Claude**: `claude-haiku-4.5` ($1.00 input / $5.00 output) — Fast, structured JSON generation. - **OpenAI**: `gpt-5.6-luna` ($0.20 input / $1.20 output) / `gpt-5.4-nano` ($0.20 input / $1.25 output) — Micro-agent execution. --- ## 3. Valstorm AI Agent Model Allocations Below is the proposed allocation for all **49 AI Agents** currently configured in Valstorm. ### Part A: Core Swarm Profiles (18 Agents) | Profile Slug (`api_name`) | Display Name | Proposed Tier | Proposed Model | Proposed Provider | Rationale | |---|---|---|---|---|---| | `orchestrator` | Valstorm Orchestrator | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Master coordinator; requires fast tool calls, context synthesis, and swarm task delegation. | | `architect` | Software Architect | **Tier 1: Heavy Thinker** | `gemini-3.1-pro-preview` | `gemini` | High-level system design, schema architecture, technical trade-offs, and specification authoring. | | `developer` | Software Developer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Monorepo coding, refactoring, test execution, debugging. | | `api-dev` | FastAPI Backend Developer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | FastAPI routes, MongoDB queries, Celery background workers, WebSockets. | | `frontend-dev` | Frontend React/Next.js Engineer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | React 19, Next.js, Jotai state atoms, Material-UI, and Tailwind CSS. | | `mobile-dev` | Mobile React Native Specialist | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | React Native, Expo, mobile styling, and native bridge maintenance. | | `tooling-dev` | Platform Tooling & CLI Engineer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Python CLI commands, internal SDK scripts, and developer tooling. | | `dev-ops` | DevOps & Cloud Engineer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Docker compose services, Kubernetes manifests, CI/CD pipelines. | | `devops-engineer` | DevOps Infrastructure Specialist | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Kubernetes cluster administration, Docker builds, and deployment workflows. | | `backend-tester` | Backend Test Engineer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Pytest automated testing, async fixtures, mock DBs, regression diagnosis. | | `frontend-tester` | Frontend QA Engineer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Playwright E2E testing, visual regression testing, Electron testing. | | `researcher` | Technical & Domain Researcher | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Literature review, domain reconnaissance, and grounded citations. | | `data-science` | Data Science Specialist | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Data analysis, Jupyter workflows, tabular data transformations. | | `valstorm-assistant` | Valstorm Assistant | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Interactive platform assistant for records CUD, SQL queries, and VFS file management. | | `docs-writer` | Technical Documentation Writer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Markdown docs authoring, API endpoint references, architecture specs. | | `browser-scraper` | Browser & Scraping Specialist | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Headless browser scripting, Playwright DOM parsing, data extraction. | | `video-editor` | Video & Media Editor | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Structured ffmpeg execution, media asset transcoding pipelines. | | `archivist` | Context Archivist & Ingestion Specialist | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Repository context aggregation, session log collation, Markdown dumping. | --- ### Part B: Platform Code & Logic Agents (8 Agents) | Record ID | Agent Name | Proposed Tier | Proposed Model | Proposed Provider | Rationale | |---|---|---|---|---|---| | `aia_tu15D07q3M1LiTWx` | Function Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Generates runnable Python backend functions and saves records. | | `aia_kpBGLze5fwBHMnOS` | Record Trigger Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Generates high-reliability Python record trigger logic. | | `aia_XkUOTtS9gqnelOCj` | Create Function Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Writes new backend execution functions from UI specs. | | `aia_9XJnIA6kT48wcP3p` | Update Function Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Refactors and updates existing backend execution functions. | | `aia_m8w0QTi8XCFbG6ph` | Create Record Trigger Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Authors new record trigger handlers. | | `aia_ENvuuS8MvA2QDJv8` | Update Record Trigger Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Updates and refactors existing record trigger code. | | `aia_emyNgA8qYH4hExvu` | Local Python Analyzer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Analyzes local Python code syntax, AST, and dependencies. | | `aia_v1GZ0tvTEGXfUbBj` | Create Report Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Analyzes reporting criteria and constructs complex aggregation reports. | --- ### Part C: Query & UI Builder Agents (5 Agents) | Record ID | Agent Name | Proposed Tier | Proposed Model | Proposed Provider | Rationale | |---|---|---|---|---|---| | `aia_pAerUTMagAs046Hq` | SQL Query Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Translates natural language into Valstorm SQL syntax with dynamic filters. | | `aia_jcjU825KU3MmvvFn` | SQL Query Agent (V2) | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Evaluates complex joins, ME parameters, and object filters. | | `aia_o4lDBHUSpzE0YOTu` | Query Builder Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Builds dynamic UI query structures and filter criteria. | | `aia_rptSaogzpqJc0qBF` | Valstorm Data Visualizer | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Generates data charts, graphs, and SVG/HTML visual components. | | `aia_N1QEBRteTHykdZfW` | Email Builder Agent | **Tier 2: Reasoner** | `gemini-3.7-flash` | `gemini` | Generates responsive HTML email templates and marketing copy. | --- ### Part D: Single-Task CRUD, Schema & Utility Micro-Agents (18 Agents) | Record ID | Agent Name | Proposed Tier | Proposed Model | Proposed Provider | Rationale | |---|---|---|---|---|---| | `aia_X3LorgtDZRfJeTFQ` | Schema Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Executes `get_schema` tool lookups based on entity name. | | `aia_gXJXd7cNInCkiA5E` | Create Object Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Creates custom schema object definitions. | | `aia_yRksIjS3Vakv8DRM` | Update Schema Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Updates schema metadata properties. | | `aia_0OSrGrjhVDWTJbSn` | Delete Schema Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Deletes schema definitions. | | `aia_vQFZsAjS8KCpbTYa` | Create Field Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Adds field definitions to an existing schema. | | `aia_cJikNYrE4gTVnA5v` | Update Field Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Modifies field properties (label, format, required). | | `aia_3pmJl5XYGvVbBbZP` | Delete Field Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Removes fields from a schema. | | `aia_BLLykLZWT3ulahW9` | Create Record Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Performs single or batch record creation. | | `aia_tlFsiSXCGxnKnDJ3` | Update Record Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Performs record field updates. | | `aia_Lvl2zver46JDWMxn` | Delete Record Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Deletes records by ID. | | `aia_WTtuQ8ceW4xxUVaF` | CUD Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | General-purpose record mutations. | | `aia_KgWbMiN9rpqchDvg` | Query Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Direct record lookups and simple list fetching. | | `aia_FE7tWEX1DYXAjHYz` | Create List Filter Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Creates list view filter presets. | | `aia_F3s4Ut2jhLe4THgV` | Ticket Collector | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Ingests and tags incoming support tickets. | | `aia_sdp8ApWqIyQqBMzt` | Sales Discovery Call Note Taker | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Extracts structured key takeaways from sales call transcripts. | | `aia_fl6ReFfGw6qrXJr8` | Company Data Cruncher | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Parses and normalizes incoming company firmographics. | | `aia_iGGz0VKHeoF0qVs4` | Enrich Lead from Company Website | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Extracts company data from public web text. | | `aia_GFVgBA5tRZvZgqfd` | Docs Agent | **Tier 3: Worker Bee** | `gemini-3.5-flash-lite` | `gemini` | Lookups documentation snippets. | --- ## 4. Verification and Update Scripting A migration script (`hermes-management/apply_model_updates.py`) is prepared to: 1. Update the live `ai_agent` records in Valstorm (via authenticated `ValstormApiClient.records_update`). 2. Update the local profile files in `~/.valstorm/profiles/.json`. 3. Update `apps/agent-runtime/exports/ai_agents_migrated.json`. 4. Update `apps/agent-runtime/core/context.py` built-in profile defaults. --- --- To use Valstorm, you pay a base monthly fee of $100 for your company to be a member. From there, you purchase credits that are used based on the actions, AI, and data storage that your company uses. We believe this to be a fair approach where you pay for what you get. - There are no user seats. - The are no user licenses, you have access to all the features - You pay usage via "Credits" for any actions ## What are Credits? 1 credit = $1 USD. This is intentional to make it simple to talk in terms of dollars. Most actions you take cost $0.001, some may cost $1, others $0.01 and so on. As of writing, we are fine tuning the pricing model as more data becomes available. --- [_index.md](cloud://file_xnmazCk626Bgl6gZ) -> Table of Contents [Home](cloud://file_JTt9db0GDAyvLlTU) -> Essentials of Pricing --- This guide explains how users and AI agents collaborate within Valstorm to plan, scaffold, execute, and review structured work using **Projects**, **Phases**, and **Tasks**. Whether you are prompting an AI agent to build out a complex project plan from scratch or kicking off autonomous execution, this document serves as the operational contract for both humans and AI. *** ## 1. The Core Mental Model Valstorm bridges human strategic vision and autonomous agent execution through a 3-tier hierarchy: 1. **Project (`project`)**: The top-level container for a major initiative or deliverable (e.g., *"Migrate Cloud Infrastructure"* or *"Launch Q3 Marketing Campaign"*). It holds the master execution pipeline (`stage_order`) and tracks overall progress. 2. **Phase (`phase`)**: Major sequential milestones within a project (e.g., *"Phase 1: Discovery & Audit"*). **Rule:** All tasks within a phase must be completed before the system or user triggers a review to advance to the next phase. 3. **Task (`task`)**: Individual, actionable units of work assigned to phases (e.g., *"Export legacy DNS records"*). Tasks track status (`Not Started`, `In Progress`, `Complete`, `Blocked`) and progress percentages. *** ## 2. How AI Agents Build Projects (Scaffolding) When you ask an AI agent to set up a project, the agent uses Valstorm's structured APIs to automatically scaffold the entire hierarchy in seconds. ### Example User Prompt: > *"Hey Valstorm, I need to plan a security audit for our staging environment. Create a project with three phases: Audit, Remediation, and Verification, with specific tasks for each."* ### What the AI Agent Does Behind the Scenes: 1. **Creates the Project Record:** Calls the database to create a new `project` with a title, description, and initial status (`Draft` or `In Progress`). 2. **Scaffolds Phases & Tasks:** Creates `phase` records linked to the project and populates each phase with structured `task` items. 3. **Establishes Execution Pipeline:** Automatically populates the project’s `stage_order` array so the AI and user have a strict, ordered roadmap to follow. 4. **Links AI Chat Context:** Associates a dedicated `ai_chat` session with the project so all subsequent agent actions and telemetry are tracked. *** ## 3. Autonomous Execution Workflow Once a project is scaffolded, the workflow follows a cyclical, human-in-the-loop pattern designed for autonomous agent work: ### Step 1: Kickoff & Initialization - **Action:** The user (or AI upon prompt) initiates the project execution (`action="start"`). - **AI Behavior:** The AI agent loads the project context, inspects `current_stage`, identifies the active phase and its pending tasks, and prepares to execute the work. ### Step 2: Agentic Work Execution - **Action:** The AI agent works through the tasks in the current active phase. It can run code, interact with APIs, generate documents, or perform system operations. - **Tracking:** As tasks are worked on, their status updates to `In Progress` and eventually `Complete` (with progress set to `100%`). ### Step 3: Phase Review & Advancement - **Action:** Once all tasks in the current phase are marked complete, the project pauses for review. - **User Review:** The user inspects the completed phase outputs in the **Project Dashboard** (`ProjectDashboardView`). - **Advancement (`action="advance"`):** Once approved, the user (or agent) triggers the stage advancement. The system marks the current phase as `Complete` and automatically shifts `current_stage` to the next phase in the pipeline. *** ## 4. Best Practices for Prompting AI Agents on Projects To ensure your AI agents successfully manage and execute projects without getting stuck, follow these prompting guidelines: ### 1. Be Specific About Milestones and Phases - *Weak Prompt:* "Help me build a website." - *Strong Prompt:* "Create a Valstorm project called 'Marketing Website Redesign' with 3 distinct phases: 1. Wireframing, 2. Frontend Development, 3. SEO Optimization. Include at least 3 detailed tasks per phase." ### 2. Instruct the Agent to Use the Project Orchestrator - When working with an active project, tell the agent: > *"Start the project orchestration for project ID `proj_...`, review the current active phase, and execute the next pending task."* ### 3. Leverage Human Review Gates - Remind the agent that **Phases are review gates**. Instruct the agent: > *"Complete all tasks in Phase 1, mark the phase as ready for review, and pause until I approve the transition to Phase 2."* *** ## Summary for the Public Knowledge Base By standardizing Projects, Phases, and Tasks, Valstorm enables users to delegate entire operational workflows to AI agents. You talk, the AI scaffolds the roadmap, the agent executes task-by-task, and you maintain complete control at every phase review gate. --- Welcome to the **Valstorm Desktop VFS Navigation Guide**. This document covers all user interactions, multi-selection workflows, drag-and-drop operations, and keyboard navigation available in the modern file tree interface. --- ## 1. Visual Overview & Layout The Valstorm file tree brings a **Linear / VS Code / Obsidian-inspired** aesthetic designed for high density, visual clarity, and instant feedback. ```text ┌────────────────────────────────────────────────────────────┐ │ 📁 CLOUD VAULTS [🔍] [📄+] [📁+] [⊟] │ ← Interactive Header ├────────────────────────────────────────────────────────────┤ │ ▾ 📁 Projects │ │ │ │ │ ← Tree Guide Line │ │ ├── 📄 roadmap.md [📄+] [📁+] │ ← Quick Action Hover │ │ └── ▾ 📁 src │ │ │ │ ├── 📄 index.ts │ │ │ │ └── 📄 App.tsx │ ← Floating Selection Pill │ ▾ 📁 Notes │ │ └── 📄 meeting-notes.docx │ └────────────────────────────────────────────────────────────┘ ``` ### Key Visual Features: * **Dynamic SVG Iconography:** Extension-specific colored glyphs (`.ts`, `.json`, `.md`, images, spreadsheets) and distinct open/closed folder states (`Folder` vs `FolderOpen`) with rotating chevrons. * **Floating Selection Pills:** Clean `28px` row density with rounded floating selection highlights (`rounded-md mx-1.5`) that provide clear visual focus without clipping window borders. * **Indentation Guide Lines:** 1px vertical nesting lines (`border-l`) connecting parent folders to children with active-branch hover illumination. * **Quick Action Hover Toolbars:** Instant action buttons (`+ File`, `+ Folder`, `Delete`) appear seamlessly when hovering over any directory row. --- ## 2. Selection & Multi-Selection Workflows Valstorm VFS supports comprehensive single and multi-item selection techniques: | Interaction | Gesture / Shortcut | Description | | :--- | :--- | :--- | | **Single Select & Toggle** | `Click` on file or folder | Selects the item. Opens the file in the editor or expands/collapses the folder. | | **Toggle Multi-Select** | `Cmd + Click` *(Mac)*
`Ctrl + Click` *(Win)* | Toggles individual files/folders in or out of the selection without deselecting other items. | | **Contiguous Range Select** | `Shift + Click` | Selects all items in between the last-clicked item and the newly clicked item. | | **Append Range to Selection** | `Cmd + Shift + Click` | Appends a new contiguous range of items to an existing non-contiguous selection. | | **Marquee Rubber-Band Drag** | `Shift` / `Cmd` / `Option` + `Drag` | Draws a live blue marquee box (`bg-blue-500/15`) selecting all intersecting items across the tree. | | **Native Item Move (DND)** | `Click & Drag` on any item | Drags the file/folder (or multi-selected items) to move or reorder in the tree. | --- ## 3. Drag & Drop Interactions The VFS features precise visual indicators for organizing and reordering items: ### 3.1. Moving into Folders * **Action:** Drag a file, folder, or multi-selection over a folder row. * **Visual Cue:** The folder highlights with a glowing blue inner bounding ring (`ring-2 ring-blue-500 bg-blue-500/10`). * **Result:** Releasing the mouse drops the items inside that folder. ### 3.2. Inserting Between Items * **Action:** Drag an item slightly above or below another file/folder row. * **Visual Cue:** A sharp `2px` horizontal blue insertion line appears with a depth indicator dot showing the exact target position. * **Result:** Releasing the mouse reorders the item at that position. ### 3.3. Multi-Item Dragging * **Action:** Multi-select multiple files (via `Cmd+Click`, `Shift+Click`, or Marquee Drag), then click and drag any selected item. * **Visual Cue:** Custom drag ghost overlay displaying the batch drag operation with subtle tilt and elevation shadow. * **Result:** All selected files and folders are moved together to the drop target. ### 3.4. External File Upload * **Action:** Drag files directly from your operating system's desktop/file manager into the tree. * **Visual Cue:** Tree container highlights with a dashed upload dropzone. * **Result:** Files are automatically uploaded into the targeted vault. --- ## 4. Context Menu & Actions Right-clicking on any file or folder opens a glassmorphism floating context menu (`backdrop-blur-xl bg-white/90 dark:bg-zinc-900/90 shadow-2xl rounded-xl`): * **New File...** — Prompts inline filename creation inside the folder. * **New Folder...** — Prompts inline directory creation. * **Rename (`F2` / `Enter`)** — Converts the row into an inline input field with the filename pre-selected. * **Copy Path (`Cmd + C`)** — Copies the full VFS / cloud path to your clipboard. * **Delete (`Cmd + Backspace` / `Del`)** — Prompts confirmation to delete single items or the entire active multi-selection. --- ## 5. Search & Filtering * **Instant Search Toggle:** Click the **Search (🔍)** icon in the tree header to transform the header title into a borderless, focused filter input. * **Live Filtering:** Type any query to instantly filter the tree view in real-time. * **Clear Filter:** Press `Escape` or click `✕` to restore the full tree hierarchy. --- ## 6. Keyboard Shortcuts Cheat Sheet | Key Shortcut | Action | | :--- | :--- | | `ArrowUp` / `ArrowDown` | Move selection focus up and down through visible nodes. | | `ArrowRight` | Expand collapsed folder, or jump to the first child of an open folder. | | `ArrowLeft` | Collapse open folder, or jump up to the parent folder. | | `Enter` | Open file in editor (or trigger inline rename). | | `F2` | Trigger inline Rename mode. | | `Cmd + Backspace` *(Mac)*
`Delete` *(Win)* | Delete selected file(s) / folder(s). | | `Cmd + C` *(Mac)*
`Ctrl + C` *(Win)* | Copy selected file / vault path to clipboard. | | `Escape` | Cancel inline creation/rename mode or clear search filter. | | *Alphanumeric Keys (`a-z`, `0-9`)* | **Type-Ahead Jump**: Instantly focuses the first node matching the typed character. | --- This guide covers how to enable and use the **native, built-in speech-to-text (voice dictation)** engines on both **macOS** and **Windows**. With native dictation enabled, you can speak directly into **any terminal, code editor, browser input, Slack message, or document** across your operating system without needing third-party software or active cloud subscriptions. --- ## Part 1: macOS Native Dictation macOS includes on-device neural dictation that works in every application, including Terminal, iTerm2, VS Code, and Valstorm Desktop. ### 1. Enabling Dictation on macOS 1. Open **System Settings** (Apple Menu  → **System Settings**). 2. In the left sidebar, click **Keyboard**. 3. Scroll down to the **Dictation** section and toggle **Dictation** to **On**. 4. If prompted, confirm **Enable Dictation**. ### 2. Configuring Your Shortcut In the same **Dictation** settings: - **Shortcut:** Select your preferred activation method: - **Press Fn (Globe 🌐) Key Twice** *(Default & Recommended)* - **Press Control Key Twice** - **Press Either Command Key Twice** - **Customize** (set any hotkey combo you want) - **Language:** Ensure your primary spoken language (e.g., English - United States) is selected. ### 3. How to Use in Terminal & Any App 1. Click into your **Terminal**, shell prompt, or any text field. 2. Press the shortcut (e.g., tap **Fn / Globe 🌐 twice**). 3. A floating microphone icon will appear next to your cursor. 4. Speak naturally (e.g., *"git status and press enter"* or *"create a new pull request for authentication"*). 5. Press **Fn / Globe 🌐** again (or **Esc** / **Return**) to finish dictation. ### 4. Essential macOS Dictation Commands for Developers macOS understands formatting and punctuation voice commands: | To Insert / Format | What to Say | | :--- | :--- | | **New Line / Enter** | *"New line"* or *"New paragraph"* | | **Punctuation** | *"Period"*, *"Comma"*, *"Colon"*, *"Semicolon"* | | **Quotes & Brackets** | *"Open quote"* / *"Close quote"*, *"Open bracket"* / *"Close bracket"* | | **Code Symbols** | *"Hyphen"*, *"Underscore"*, *"Slash"*, *"Backslash"*, *"Tilde"*, *"Pipe"* | | **ALL CAPS** | *"All caps [word]"* (e.g., *"All caps API"* → `API`) | | **snake_case / No Spaces** | *"No space on [text] no space off"* | | **Delete / Backspace** | *"Delete that"* | --- ## Part 2: Windows 11 & Windows 10 Native Voice Typing Windows includes **Windows Voice Typing** (powered by Microsoft Speech Services) that works universally across Windows Terminal, PowerShell, CMD, VS Code, and all desktop applications. ### 1. Enabling Voice Typing on Windows 1. Open **Settings** (Press `Win + I`). 2. Go to **Time & language** → **Speech**. 3. Under *Speech recognition*, ensure microphone access is allowed. 4. Go to **Privacy & security** → **Microphone** and ensure *Let apps access your microphone* is toggled **On**. ### 2. Triggering Voice Typing * **Universal Shortcut:** Press **`Windows Key + H`** (`Win + H`). ### 3. Enabling Auto-Punctuation & Launcher Bar 1. Press **`Win + H`** anywhere to bring up the Voice Typing bar. 2. Click the **Settings (Gear ⚙️)** icon on the small floating bar. 3. Toggle **Auto-punctuation** to **On** (Windows will automatically insert periods and commas based on your pauses). 4. Toggle **Voice typing launcher** to **On** if you want the mic icon to automatically appear whenever you focus a text box. ### 4. How to Use in Windows Terminal & Apps 1. Click into **Windows Terminal**, **PowerShell**, **Command Prompt**, or any input. 2. Press **`Win + H`**. 3. The Voice Typing overlay will appear and display *"Listening..."*. 4. Speak your text or commands. 5. Press **`Win + H`** again (or click the microphone icon) to stop. ### 5. Essential Windows Voice Typing Commands | To Insert / Format | What to Say | | :--- | :--- | | **New Line / Enter** | *"New line"* or *"Press Enter"* | | **Punctuation** | *"Period"*, *"Comma"*, *"Exclamation mark"*, *"Question mark"* | | **Symbols** | *"Hyphen"*, *"Underscore"*, *"Forward slash"*, *"Backslash"*, *"At sign"* (`@`), *"Hash"* (`#`) | | **Select & Edit** | *"Select previous word"*, *"Clear selection"*, *"Delete that"* | | **Pause Dictation** | *"Stop listening"* or *"Pause voice typing"* | --- ## Summary: Built-in OS Dictation vs. Cloud STT (Deepgram Nova-3 / Whisper) | Feature | Built-in OS Dictation (macOS / Windows) | Cloud STT (Deepgram Nova-3 / Azure / Whisper) | | :--- | :--- | :--- | | **Cost** | 100% Free & Built-in | API usage cost per audio minute | | **Setup** | Built into macOS & Windows settings | Requires API keys and background integration | | **Availability** | Works across every desktop app and terminal | Works in supported apps or via custom background daemons | | **Offline Support** | **Yes** (Runs on-device via Apple Neural Engine / Windows Speech) | **No** (Requires internet / WebSocket stream) | | **Code & Technical Vocabulary** | Good for natural English and standard shell words | **Exceptional** (Optimized for complex variables, APIs, and dev jargon) | | **Latency** | Instantaneous streaming | Ultra-low latency (~200ms WebSocket) | --- ## Recommended Developer Workflow - For **general terminal usage, quick notes, Slack messages, and fast prompt writing**: Use your OS native shortcut (**Fn twice** on Mac, **Win + H** on Windows). - For **Valstorm in-app document authoring and AI Chat sessions**: Use the unified **`Cmd + M` / `Ctrl + M`** shortcut to leverage high-accuracy Deepgram Nova-3 real-time transcription directly inside the BOS workspace. --- *User Guide & Best Practices* --- ## 1. Introduction & Overview Valstorm's **Virtual File System (VFS) Hybrid Search** is a next-generation workspace intelligence engine. It combines: 1. **Sub-millisecond Metadata Search:** Instantly matches filenames, folder paths, modules, and file extensions as you type. 2. **Semantic Vector Search:** Understands the *meaning* and *context* of your documents, allowing you to find files even if you don't remember the exact name. 3. **Workspace AI Assistant (RAG):** Ask natural-language questions across your cloud files and receive immediate, factual answers grounded strictly in your team's knowledge base, complete with clickable source citations. --- ## 2. Getting Started: The Spotlight Search Bar ### 2.1 Opening Search - **Keyboard Shortcut (Mac):** Press `⌘K` anywhere in Valstorm Desktop. - **Keyboard Shortcut (Windows/Linux):** Press `Ctrl + K`. - **Top Header Bar:** Click the search bar at the top of the application window. ``` ┌────────────────────────────────────────────────────────────────────────┐ │ 🔍 Search cloud files, contracts, invoices, or ask AI... ⌘K │ └────────────────────────────────────────────────────────────────────────┘ ``` --- ## 3. The 3 Ways to Search ### Mode 1: Exact Filename & Path Navigation (`Exact Lookup`) Type any part of a file title, vault folder, or extension to immediately pull up matches with zero latency. * **Examples:** - `invoice 2026` $\to$ Returns all 2026 client & vendor invoices. - `Lamm Migration` $\to$ Jumps straight to `Lamm Data Migration.md`. - `.pdf` or `.json` $\to$ Filters files matching that filetype. --- ### Mode 2: Semantic Concept Search (`Semantic Search`) When you don't remember a filename but remember what the document is about, describe the concept in plain English. The AI engine searches the underlying text embeddings across all indexed cloud files. * **Examples:** - `confidential obligations and non disclosure` $\to$ Automatically finds and highlights snippets from `Valstorm_Mutual_NDA_Signable.pdf`. - `cryogenic temperatures for quantum systems` $\to$ Retrieves research notes containing physics excerpts. - `steps for database cutover during migration` $\to$ Finds the operational migration runbooks. --- ### Mode 3: Natural Language Questions & Workspace AI Answers (`RAG Query`) Need a direct answer without opening and skimming through multiple 20-page PDFs? Just ask a full question! * **Examples:** - `What are the payment terms in the mutual NDA?` - `How do we configure call routing in ValPhone?` - `What were the key takeaways from the Bob & Jared meeting on August 1st?` #### How AI Workspace Answers Work: 1. **Instant Document Retrieval:** The engine pulls the most relevant document chunks across your organization. 2. **Live Token Streaming:** A dedicated AI model (`gemini-flash-lite-latest`) streams a concise, direct answer into an AI Workspace Answer card. 3. **Verified Sources:** Every answer displays clickable **Source Chips** (e.g. `[ 📖 Mutual_NDA.pdf ]`). Clicking any source opens the exact document directly in your Valstorm Editor or Preview pane. ``` ┌────────────────────────────────────────────────────────────────────────┐ │ ✨ AI WORKSPACE ANSWER │ │ The payment terms outlined in the agreement specify net-30 upon receipt│ │ of invoice, with late penalties accruing after 45 days. │ │ │ │ Sources: [ 📖 Mutual_NDA.pdf ] [ 📖 Sales_Quote_Template.pdf ] │ └────────────────────────────────────────────────────────────────────────┘ ``` --- ## 4. Keyboard Shortcuts & Power Navigation | Key / Shortcut | Action | |---|---| | `⌘K` / `Ctrl+K` | Open or focus the Spotlight Search modal from anywhere | | `Arrow Down (↓)` | Move selection down through search results (auto-scrolls list) | | `Arrow Up (↑)` | Move selection up through search results | | `Enter (↵)` | Open the currently selected file or navigate to the selected module | | `Escape (Esc)` | Close the search modal and clear current query | | `Click on Source Chip` | Jump directly to the cited cloud document | --- ## 5. Supported Document Formats All text, code, document, and data files are automatically indexed for semantic search and AI answers upon upload: | Category | Supported Extensions | |---|---| | **Documents & Spreadsheets** | `.pdf`, `.docx`, `.doc`, `.rtf`, `.epub`, `.csv`, `.tsv`, `.xlsx` | | **Notes & Text** | `.md`, `.markdown`, `.txt`, `.log`, `.ini`, `.conf`, `.toml`, `.yaml`, `.yml` | | **Code & Technical Assets** | `.py`, `.js`, `.ts`, `.tsx`, `.jsx`, `.sh`, `.bash`, `.sql`, `.html`, `.css`, `.json`, `.graphql` | | **Subtitles & Transcripts** | `.srt`, `.vtt`, `.json` | *Note: Media formats like `.png`, `.jpg`, `.mp4`, `.mp3`, and `.heic` are indexed by filename and metadata, while their binary bodies are preserved without consuming vector index capacity.* --- ## 6. Real-Time Indexing & Data Lifecycle - **Instant Upload Indexing:** Whenever you upload a document, save changes in the Editor, or create a new file version, background Celery workers automatically extract text and update vector embeddings in real time (~100–500ms). - **Automatic Vector Cleanup:** When you delete a file or vault, all associated vector chunks and embeddings are instantly purged from the search index. - **Tenant Isolation:** Every search query and vector lookup is strictly partitioned by your organization's ID (`org_id`), ensuring data never leaks across organization boundaries. --- This guide provides a comprehensive list of all functions available for use in **Formula Fields** and other calculation engines within ValStorm. ## 📋 Syntax Overview * **Variables:** Access record fields using `{{record.field_api_name}}`. * **Strings:** Use single (`'`) or double (`"`) quotes for text. * **Booleans:** Use `TRUE`, `FALSE`, `true`, or `false`. * **Logical Operators:** * **AND**: Use `AND`, `and`, or `&&`. * **OR**: Use `OR`, `or`, or `||`. * **Comparison Operators:** * **Equal**: Use `=` or `==`. * **Not Equal**: Use `!=` or `<>`. * **Concatenation:** Use the `&` operator to join strings. --- ## 🧠 Logical Functions ### IF Returns one value if a condition is true and another if it is false. * **Syntax:** `IF(condition, true_value, false_value)` * **Example:** `IF({{record.amount}} > 1000, "High Value", "Standard")` ### AND Returns TRUE if all arguments are true. * **Syntax:** `AND(logical1, [logical2], ...)` * **Example:** `AND({{record.is_active}}, {{record.score}} > 50)` * **Alternative:** `{{record.is_active}} && {{record.score}} > 50` ### OR Returns TRUE if at least one argument is true. * **Syntax:** `OR(logical1, [logical2], ...)` * **Example:** `OR({{record.status}} = "Closed", {{record.status}} = "Archived")` * **Alternative:** `{{record.status}} == "Closed" || {{record.status}} == "Archived"` ### NOT Inverts the value of its argument. * **Syntax:** `NOT(logical)` * **Example:** `NOT({{record.is_deleted}})` --- ## 🔤 Text Functions ### UPPER Converts a string to all uppercase letters. * **Syntax:** `UPPER(text)` * **Example:** `UPPER({{record.last_name}})` ### LOWER Converts a string to all lowercase letters. * **Syntax:** `LOWER(text)` * **Example:** `LOWER({{record.email}})` ### LEN Returns the number of characters in a text string. * **Syntax:** `LEN(text)` * **Example:** `LEN({{record.phone}})` ### FORMAT_CURRENCY Formats a number as a currency string. * **Syntax:** `FORMAT_CURRENCY(number)` * **Example:** `FORMAT_CURRENCY({{record.total_price}})` -> `"$1,234.56"` --- ## 🔢 Math Functions ### SUM Adds all the numbers in a list of arguments. * **Syntax:** `SUM(number1, [number2], ...)` * **Example:** `SUM({{record.subtotal}}, {{record.tax}}, {{record.shipping}})` --- ## 📅 Date & Time Functions ### TODAY Returns the current date in ISO format (YYYY-MM-DD). * **Syntax:** `TODAY()` * **Example:** `IF({{record.due_date}} < TODAY(), "Overdue", "On Time")` ### NOW Returns the current date and time in ISO format. * **Syntax:** `NOW()` ### AGE Calculates the age in years based on a birth date. * **Syntax:** `AGE(date)` * **Example:** `AGE({{record.birthday}})` -> `31` ### NEXT_BIRTHDAY Calculates the next occurrence of a birthday. * **Syntax:** `NEXT_BIRTHDAY(date)` * **Example:** `NEXT_BIRTHDAY({{record.birthday}})` -> `"2026-07-28"` ### DATE_ADD Adds a specific number of units to a date. * **Syntax:** `DATE_ADD(date, amount, unit)` * **Units:** `'day'`, `'week'`, `'month'`, `'year'`, `'hour'`, `'minute'` * **Example:** `DATE_ADD({{record.created_date}}, 30, 'days')` ### DATE_DIFF Calculates the difference between two dates. * **Syntax:** `DATE_DIFF(end_date, start_date, unit)` * **Units:** `'day'`, `'hour'`, `'minute'`, `'second'`, `'year'` * **Example:** `DATE_DIFF(NOW(), {{record.last_contact_date}}, 'days')` --- ## 🔍 Utility & Inspection ### ISNULL Returns TRUE if the value is null or missing. * **Syntax:** `ISNULL(value)` * **Example:** `IF(ISNULL({{record.manager}}), "No Manager", {{record.manager.name}})` ### ISBLANK Returns TRUE if the value is null, missing, or an empty string. * **Syntax:** `ISBLANK(value)` * **Example:** `IF(ISBLANK({{record.middle_name}}), "None", {{record.middle_name}})` --- The Valstorm Function System allows you to write, manage, and execute custom Python code within the platform. Functions are versatile building blocks that can be used for complex data processing, integrations, AI orchestrations, and scheduled tasks. ## Overview A "Function" in Valstorm is a snippet of Python code executed securely in a sandboxed environment. Every function must define an asynchronous `execute` method that receives a `PlatformContext` object, giving it unified access to database operations, external integrations, and communication tools. ### Key Capabilities - **Database Operations:** Full CRUD and querying (SQL and MongoDB pipelines). - **Communication:** Sending SMS, emails, and UI notifications. - **Integrations:** Access to Google Workspace, Salesforce, Twilio, and Microsoft integrations. - **Workflows:** Triggering other automations or calling other functions. - **Scheduling:** Functions can be executed dynamically or on a schedule via Scheduled Items. --- ## Writing a Function ### Basic Structure Every function must have an `async def execute` method. The `PlatformContext` is injected into this method, alongside any custom arguments passed by the invoker. ```python from valstorm_platform.platform_context import PlatformContext async def execute(platform: PlatformContext, **kwargs): """ Example function that creates a task. """ try: # Retrieve custom arguments lead_id = kwargs.get('lead_id') task_name = kwargs.get('task_name', 'Default Task') if not lead_id: platform.log("Missing lead_id", "error") return {"status": "error", "message": "lead_id is required"} # Use the platform API to interact with the system await platform.records.create('task', { "name": task_name, "status": "Not Started", "related_to": { "id": lead_id, "schema": "lead" } }) platform.log(f"Successfully created task for lead {lead_id}", "info") return {"status": "success"} except Exception as e: platform.log(f"Error in my_function: {str(e)}", "error") return {"status": "error", "message": str(e)} ``` ### The `PlatformContext` The `PlatformContext` object is your gateway to Valstorm's internal APIs. It provides a clean, unified interface. * `platform.records`: `.create()`, `.update()`, `.delete()` records. * `platform.query`: `.sql()` for standard querying, `.mongo()` for complex aggregation pipelines. * `platform.communications`: Send SMS (`.send_sms()`), UI notifications (`.notifications.notify()`). * `platform.workflows`: Trigger workflows (`.run_workflow()`) or other functions (`.run_function()`). * `platform.integrations`: Access third-party services (e.g., `platform.integrations.google`, `platform.integrations.salesforce`). * `platform.metadata`: Access org-specific settings (`.get_config()`). * `platform.log()`: Write to the system logs. * `platform.user`: The current executing `User` object. --- ## Invoking Functions Functions can be invoked from multiple places within the Valstorm ecosystem. ### 1. Via Record Triggers You can call a function from within a V2 Record Trigger using the `platform.workflows.run_function()` method. ```python async def execute(context: RecordTriggerContext) -> None: # Trigger a custom function asynchronously await context.workflows.run_function( function_name="my_custom_function.py", kwargs={ "lead_id": context.new_map["some_id"]["id"] } ) ``` ### 2. Via Automations (Workflows) Automations (Flow Builder) can include a "Function Node" that executes a specific function. The node can pass dynamic variables from the automation's context into the function as `kwargs`. ### 3. Via API Request Functions can be triggered directly via the Valstorm API using the `/v1/automation/function` endpoint. **POST** `/v1/automation/function` ```json { "function_name": "my_custom_function.py", "inputs": { "lead_id": "12345", "task_name": "Follow up call" } } ``` ### 4. Via Scheduled Items You can create a `scheduled_item` record to execute a function at a specific date and time. ```python # Creating a scheduled execution await platform.records.create('scheduled_item', { "name": "Run My Function Tomorrow", "run_date_time": "2026-04-16T10:00:00Z", "status": "Queued", "function": { "id": "function-record-id", }, "data": { "lead_id": "12345" } }) ``` --- ## Security & Execution Context - **Sandboxing**: Functions are loaded and executed inside a `FunctionProxy` that ensures AST (Abstract Syntax Tree) validation to prevent malicious operations. - **Timeouts**: Execution is wrapped in a strict timeout to prevent long-running loops from blocking system resources. - **Multi-Tenancy**: The `PlatformContext` is strictly bound to the `current_user` and their organization. Functions inherently cannot access data outside their tenant. --- Your ability to view and interact with data in the system is governed by a powerful and flexible security model. This model is built on two core concepts: **Permissions** and the **Role Hierarchy**. Understanding how they work together is key to understanding what you can see and do. * **Permissions** control *what actions* you can perform (like viewing, creating, or editing). * The **Role Hierarchy** controls *which records* you have access to (like your own records, your team's records, or everyone's records). ----- ## Permissions: Controlling Your Actions Permissions define your fundamental capabilities for each object and field in the system. They are the "verbs" of the security model—the actions you are allowed to take. ### Object & Field Level Control Permissions operate on two levels: 1. **Object Permissions**: These are high-level controls that grant or deny access to an entire object (e.g., 'Account', 'Contact', 'Opportunity'). The four main actions are: * **Create**: The ability to create new records of that object type. * **Read**: The ability to view records of that object type. * **Update**: The ability to modify existing records. * **Delete**: The ability to remove records. 2. **Field Permissions**: For more granular control, administrators can set permissions on individual fields within an object. This allows them to protect sensitive information. The two field-level actions are: * **Read**: The ability to see the value in a specific field. * **Update**: The ability to change the value in a specific field. For example, you might have `Update` permission on the 'Opportunity' object but be denied `Update` permission on the `Amount` field, making it read-only for you. ### Permission Stacking and Precedence You can be assigned multiple sets of permissions. When permissions conflict, the system uses a simple rule: **the highest level of access wins**. If any permission set grants you access to an action, you will have that access, even if another permission set denies it. This "stacking" allows for flexible security. A base permission set can be applied to all users, with more privileged sets layered on top for specific groups. - Update Opportunity: True
- Update Amount Field: False] B[Sales Manager Add-on
- Update Amount Field: True] end subgraph "User Assignment" C(User is assigned BOTH sets) end subgraph "Final Access" D{Effective Permissions
- Update Opportunity: True
- Update Amount Field: True} end A --> C B --> C C --> D style D fill:#d4edda,stroke:#c3e6cb `} /> *In the diagram above, the user gains the ability to update the 'Amount' field because the "Sales Manager Add-on" permission overrides the restriction from the base "Sales Rep" set.* ----- ## Role Hierarchy: Controlling Data Visibility While permissions define *what you can do*, roles define *which records you can see*. The role hierarchy is typically modeled after your organization's structure, creating a parent-child relationship between roles (e.g., a Sales Manager is the parent role to several Sales Rep roles). This structure enables data to "roll up," meaning users higher in the hierarchy gain visibility into the records of users below them. Data access is determined by the **View Access** level set on your role. Full Access) VP_Sales(VP of Sales
Subordinate Access) Sales_Mgr1(Sales Mgr A
Subordinate Access) Sales_Mgr2(Sales Mgr B
Subordinate Access) Rep1(Sales Rep 1
Team Access) Rep2(Sales Rep 2
Team Access) Rep3(Sales Rep 3
Personal Access) Rep4(Sales Rep 4
Team Access) VP_Sales --> CEO Sales_Mgr1 --> VP_Sales Sales_Mgr2 --> VP_Sales Rep1 --> Sales_Mgr1 Rep2 --> Sales_Mgr1 Rep3 --> Sales_Mgr2 Rep4 --> Sales_Mgr2 style CEO fill:#cce5ff,stroke:#b8daff `} /> ### Data Access Levels Your role will have one of the following access levels, which determines the records you can see based on record ownership. * **Personal**: You can only view and edit records that you personally own. You cannot see records owned by your colleagues, even those with the same role. * *Example*: `Rep 3` can only see their own records. * **Team**: You can view and edit records owned by you AND any other user who shares the same role. * *Example*: `Rep 1` and `Rep 2` can see each other's records because they share the "Sales Mgr A" team. * **Subordinate**: You can view and edit records owned by you, your team, AND anyone in roles beneath you in the hierarchy. This is the standard for managers. * *Example*: `Sales Mgr A` can see records owned by `Rep 1` and `Rep 2`. The `VP of Sales` can see records owned by both `Sales Mgr A` and `Sales Mgr B` and all their subordinate reps. * **Full**: You can see all records for an object, regardless of who owns them. This is typically reserved for administrators or executive roles. * *Example*: The `CEO` can see every record in the system. ----- ## Record-Level Sharing: Granular Exceptions Sometimes, you need to collaborate on a specific record with someone who wouldn't normally have access to it based on the Role Hierarchy. This is where **Record-Level Sharing** comes in. Record-Level Sharing allows you to explicitly grant access to a single record to individual users, completely bypassing the standard Role Hierarchy. ### How Sharing Works When you share a record with another user, you assign them a specific **Access Level** for that record: * **Read**: The user can view the record but cannot make any changes. * **Edit**: The user can view and modify the record's data. * **Delete**: The user has full control, including the ability to permanently delete the record. **Important Rules of Sharing:** * **Ownership Precedence**: The owner of a record (and administrators) always maintains full control. * **Hierarchy Bypass**: Sharing a record grants access *across* or *up* the hierarchy. For example, a Sales Rep (`Personal` access) can share a specific Deal record with another Sales Rep on a different team, allowing them to collaborate on that one deal without exposing their entire pipeline. * **Strict Enforcement**: Sharing permissions are strictly enforced. If a user is granted `Read` access via sharing, the system will actively block them from saving edits or deleting the record, even if their broader Object Permissions technically allow those actions. The sharing access level acts as a ceiling for that specific record. --- ## API Scopes In addition to the standard permissions and role hierarchy, API access is controlled through **API Scopes**. These scopes define what actions anyone can perform when interacting with the system. API Scopes are added to permissions. For example, you might have a permission set that grants `Read` access to the 'Contact' object, but if the API Scope for the endpoint to interact with objects is not included, then you won't be able to access that data through the API. These also work on Integrated Apps, which restrict down external applications and oauth connections to only the API Scopes assigned to that app. This allows you to safely integrate with third-party applications without worrying about overexposing your data. You can find every API Scope by calling a GET request to https://api.valstorm.com/v1/routes. This will return a list of all API endpoints and the scopes required to access them. ----- ### Putting It All Together Your final access is a combination of your **Permissions**, your **Role**, and any **Record-Level Sharing**. Imagine you are `Sales Mgr A`. 1. Your **Role** grants you `Subordinate` access, so you can **see** all Opportunity records owned by `Rep 1` and `Rep 2`. 2. Your **Permissions** grant you `Update` access on the Opportunity object but deny you `Delete` access. The result is that you can view and edit your team's Opportunities, but you cannot delete them. This powerful combination allows for precise and secure control over your organization's data. --- The `PlatformContext` is a unified facade for all Valstorm platform operations. It provides a standardized way to interact with various platform services such as database operations, schema management, communication, and external integrations. ## Overview The `PlatformContext` class aggregates several domain-specific contexts, each handling a particular aspect of the platform. It is designed to be injected into FastAPI routes or used within system functions to provide a consistent execution environment. ### Initialization ```python from valstorm_platform.platform_context import PlatformContext # current_user is a valstorm.models.User object platform = PlatformContext(current_user) ``` In FastAPI routes, use the dependency: ```python from valstorm_platform.platform_context import get_platform_context, PlatformContext @router.get("/my-route") async def my_route(ctx: PlatformContext = Depends(get_platform_context)): # Use ctx here ... ``` ## Sub-Contexts ### 1. `records` (RecordContext) Handles Create, Update, and Delete (CUD) operations on MongoDB collections. Supports both synchronous and asynchronous execution (via Celery). - `create(api_name, input_data, async_run=False, **kwargs)` - `update(api_name, input_data, async_run=False, **kwargs)` - `delete(api_name, input_data, async_run=False, **kwargs)` - `bulk_write(api_name, operations, **kwargs)` ### 2. `query` (QueryContext) Provides methods for querying data using SQL-like syntax or MongoDB aggregation pipelines. - `sql(query, **kwargs)`: Executes a SQL-like query. - `mongo(collection, pipeline, **kwargs)`: Executes a MongoDB aggregation pipeline. - `resolve_lookup(api_name, record_id)`: Resolves a record ID into a full lookup object. ### 3. `schemas` (SchemaContext) Provides access to object schemas. - `get(api_name)`: Get the schema for a specific object. - `list()`: List all simplified schemas for the organization. - `list_all()`: Get full schema definitions for all objects. ### 4. `communications` (CommunicationContext) Aggregates communication services like Twilio and Notifications. - `send_sms(to_phone, message, **kwargs)` - `twilio`: Sub-context for advanced Twilio operations (lookup, conversation management, etc.). - `notifications`: Sub-context for sending and managing in-app notifications. ### 5. `integrations` (IntegrationContext) Provides unified access to external integrations. - `salesforce`: Query, create, and update records in Salesforce. - `google`: Access Google Workspace services (Drive, etc.). - `microsoft`: Microsoft 365 operations and event mapping. ### 6. `workflows` (WorkflowContext) Executes system functions and automation workflows. - `run_function(function_name, kwargs, **kwargs_extra)` - `run_workflow(workflow_id, data, **kwargs)` - `safe_execute(func, *args, **kwargs)`: Safely executes an async function with logging. ### 7. `files` (FileContext) Handles file operations, primarily using Amazon S3. - `upload(path, data, **kwargs)` - `delete_s3(location)` - `s3_client`: Direct access to the S3 client. ### 8. `utils` (UtilsContext) General platform utilities for data formatting and parsing. - `aware_datetime(dt)`: Ensures a datetime object is timezone-aware. - `phone_formatter(phone)`: Formats phone numbers. - `html_to_md(html)`: Converts HTML to Markdown. ### 9. `metadata` (MetadataContext) Accesses organization-level settings and configuration. - `get_config(api_name)`: Returns organization settings. ## Direct Access - `platform.db`: Provides direct access to the organization's MongoDB database. - `platform.log(message, level="info")`: Centralized logging via `valstorm.dependencies.add_log`. --- Record triggers allow you to inject custom automation into the standard Create, Update, and Delete (CUD) API request lifecycle. Modern Valstorm triggers leverage the **Platform Context**, a unified API that provides safe, high-level access to database operations, integrations, and utility functions. --- ## Trigger Lifecycle Contexts Triggers execute in one of three stages of the request lifecycle: * **Before**: Runs *synchronously* before the database operation. Use this for data validation (raising `HTTPException` to block) or modifying data in-place by updating the records in `context.new_map`. * **After**: Runs *synchronously* after the database commit but before the API response is sent. Ideal for immediate side effects that depend on the record being successfully saved. * **Async**: Runs *asynchronously* in a background worker. Perfect for heavy lifting like external API syncs, long-running calculations, or non-critical notifications. --- ## The Trigger Structure Every trigger file must implement an `execute` function. ```python from valstorm_platform.trigger_context import RecordTriggerContext async def execute(context: RecordTriggerContext): # Your logic here pass ``` ### The `RecordTriggerContext` Object The `context` object provides everything needed to process the batch: | Property | Type | Description | | :--- | :--- | :--- | | `context.user` | `User` | The authenticated user performing the action. | | `context.new_map` | `dict` | `{id: record}` map of the **new** state. | | `context.old_map` | `dict` | `{id: record}` map of the **previous** state (empty on Create). | | `context.trigger_context` | `set` | Indicates the phase and action (e.g., `{'Before', 'Update'}`). | | `context.log(msg, level)` | `method` | Standardized logging (`'info'`, `'warning'`, `'error'`). | | `context.is_changed(id, field)`| `method` | **Preferred** way to check if a specific field was modified. | --- ## Platform Context API Modules The `context` object exposes several namespaces to interact with the Valstorm ecosystem: ### 1. Data Operations (`context.records` & `context.query`) * **`await context.records.create(api_name, input_data)`**: Create one or many records. * **`await context.records.update(api_name, input_data)`**: Update one or many records. * **`await context.records.delete(api_name, input_data)`**: Delete one or many records. * **`await context.query.sql(query, bypass_cache=True)`**: Execute SQL-like queries against organization data. ### 2. Metadata & Config (`context.metadata`) * **`await context.metadata.get_config(api_name)`**: Fetches organization-specific App Metadata (e.g., "B2B Sales Config"). Returns a dictionary. ### 3. Communications (`context.communications`) * **`await context.communications.notifications.notify(list[SendNotificationSetting])`**: Send push/in-app notifications. * **`await context.communications.notifications.mark_read(data=list[dict])`**: Mark notifications as read using a list of filters (e.g., `{'record_id': '...'}`). ### 4. Integrations (`context.integrations`) * **`context.integrations.salesforce`**: `query()`, `create()`, and `update()` records in the linked Salesforce instance. * **`context.integrations.google`**: `fetch_drive_files_concurrently(file_ids)` and other Workspace helpers. ### 5. Task Execution (`context.tasks`) * **`await context.tasks.run_function(name, kwargs)`**: Execute a system function. * **`await context.tasks.run_workflow(workflow_id, data)`**: Trigger a specific automation workflow. ### 6. Utilities (`context.utils`) * **`context.utils.phone_formatter(phone)`**: Standardizes phone strings. * **`await context.utils.html_to_md(html)`**: Safely converts HTML content to Markdown. * **`context.utils.get_phone_fields(schema)`**: Identifies which fields in an object schema are phone types. --- ## Common Patterns ### Conditional Logic (Detecting Changes) Always use `context.is_changed()` instead of manual equality checks. It handles deep comparisons (e.g., nested Address objects), type normalization (Dict vs Pydantic Model), and treats `None` and missing keys as equivalent. ```python async def execute(context: RecordTriggerContext): if 'Update' in context.trigger_context: for record in context.new_map.values(): # ONLY runs if the address was actually modified in the payload if context.is_changed(record['id'], 'property_address'): # Perform address-specific logic pass ``` ### In-Place Modification (Before context) In a "Before" trigger, you don't need to call `update()`. Simply modify the objects in `new_map`. ```python async def execute(context: RecordTriggerContext): if 'Before' in context.trigger_context: for record in context.new_map.values(): if not record.get('full_name'): record['full_name'] = f"{record.get('first')} {record.get('last')}" ``` ### Side Effects with Configuration Fetch metadata to drive dynamic logic. ```python async def execute(context: RecordTriggerContext): config = await context.metadata.get_config("Lead Automation") if not config or not config.get('active'): return if 'After' in context.trigger_context: # Create a related record based on config mapping pass ``` ### Error Handling * In **Before** triggers: Raising `HTTPException` will block the DB transaction and return the error to the user. * In **After/Async** triggers: Errors should be logged via `context.log` to prevent interrupting the request or background worker. --- ## Pro-Tips & Best Practices 1. **Leverage `context.is_changed()`**: Avoid manual diffing like `if new_val != old_val`. `is_changed()` is safer because it explicitly checks the update payload and is resilient to normalization differences (e.g., a field being `None` in one object but missing in another). 2. **Never call `update()` in a Before trigger**: Modifying the dictionary in `context.new_map` directly is the most efficient way to change data before it hits the database. Calling `context.records.update` inside a Before trigger can cause infinite loops or unnecessary database overhead. 3. **Standardized Logging**: Avoid using standard `print()` statements. Use `context.log("message", level="info")` to ensure your logs are captured in the Valstorm execution history and correctly attributed to the specific trigger run. 4. **Validate with Exceptions**: To prevent a record from being saved based on custom logic (e.g., checking a credit score threshold), `from fastapi import HTTPException` and raise it within a 'Before' block. This ensures a clean rollback and provides immediate feedback to the UI. 5. **Batch Processing**: Always write your triggers to handle multiple records. Even if a single record is updated via the UI, the API often processes records in batches. Iterate through `context.new_map.values()` to ensure your logic scale correctly. 6. **Check Context Early**: Use `if 'Before' in context.trigger_context:` to guard your logic. Many trigger files are registered for multiple phases; explicit checks prevent logic from running in the wrong stage of the lifecycle. --- --- This query engine allows you to interact with your data using a familiar SQL-like syntax while offering powerful, custom extensions for handling dynamic dates, user context, nested lookups, and specialized field types like phone numbers. ## Core Syntax Overview The engine supports standard SQL clauses, including: * `SELECT` (Specific fields, `*`, or `table.*`) * `FROM` (Target object/collection) * `JOIN ... ON ...` (Multi-join support) * `WHERE` (Filters, including complex `( )`, `AND`, `OR` logic) * `ORDER BY` (Sorting via `ASC` or `DESC`) * `LIMIT` and `OFFSET` (Pagination) ## Special Context Keywords ### 1. The `ME` Keyword You can use `ME` (or `'ME'`, `"me"`) in your `WHERE` clause to automatically filter records owned by or related to the currently authenticated user. * **Example:** Find all contacts owned by the current user. ```sql SELECT name, email FROM contact WHERE owner = ME ``` ### 2. The `PHONE:` Resolver The `PHONE:` prefix allows you to search across *all* phone fields on an object simultaneously without manually chaining `OR` conditions. It supports both exact matches (`=`) and partial matches (`LIKE`). * **Example:** Find any lead where *any* phone field matches a specific number. ```sql SELECT name, phone FROM lead WHERE PHONE: = '+15551234567' ``` * **Example:** Find companies with a phone number starting with a specific area code. ```sql SELECT name, phone FROM company WHERE PHONE: LIKE '+1555%' ``` ### 3. Automatic Lookup Resolution When querying against a `lookup` or `compound_lookup` field (like `company`, `owner`, or `created_by`), you do not need to append `.id`. The engine automatically resolves `WHERE company = '123'` to `WHERE company.id = '123'`. --- ## Date & Time Special Keywords The engine features a highly intelligent datetime parser. When using these keywords with an equals sign (`=`), the engine automatically translates them into a time **range** (e.g., `>= start_of_period AND <= end_of_period`). ### Standard Relative Ranges These keywords evaluate based on the current date and time: * **Days:** `today`, `yesterday`, `tomorrow`, `this_day`, `last_day` * **Weeks:** `this_week`, `last_week`, `next_week` * **Months:** `this_month`, `last_month`, `next_month` * **Quarters:** `last_quarter`, `next_quarter` * **Years:** `this_year`, `last_year`, `next_year` * **Trailing/Future blocks:** `last_7_days`, `last_30_days`, `last_90_days`, `next_7_days`, `next_30_days`, `next_90_days` ### Parameterized Relative Ranges (N-based) You can specify the exact number of periods to look back or forward using a colon syntax (e.g., `keyword:N`). * **Days:** `last_n_days:10`, `next_n_days:5` * **Weeks:** `last_n_weeks:3`, `next_n_weeks:2` * **Months:** `last_n_months:6`, `next_n_months:12` * **Years:** `last_n_years:2`, `next_n_years:5` * **Time:** `last_n_hours:24`, `next_n_hours:4`, `last_n_minutes:15`, `next_n_minutes:30` * **Specific Weekdays:** `last_n_mondays:3`, `next_n_fridays:2` *(Works for all days of the week)* ### Contextual Specific Ranges You can pass an ISO date string to these keywords to get the full range containing that specific date. * `month_of:'2024-05-15'` (Resolves to May 1st - May 31st, 2024) * `week_of:'2024-05-15'` (Resolves to the Sunday-Saturday week containing May 15th) * `year_of:'2024-05-15'` * `day_of:'2024-05-15'` --- ## Working Examples by Object ### Leads (`lead`) **1. Recent high-value leads assigned to me:** ```sql SELECT name, status, loan_amount FROM lead WHERE owner = ME AND created_date = this_week AND loan_amount > 500000 ORDER BY loan_amount DESC ``` **2. Leads that went unresponsive in the last 30 days:** ```sql SELECT name, email, phone FROM lead WHERE status = 'Unresponsive' AND unresponsive_date_time = last_30_days ``` **3. Complex pipeline filtering (Using Parentheses):** ```sql SELECT * FROM lead WHERE (status = 'New' OR status_reason = 'Needs Follow Up') AND lead_source = 'Website' AND created_date = last_n_days:14 ``` ### Contacts (`contact`) **1. Finding a contact by a partial phone number:** ```sql SELECT first_name, last_name, email FROM contact WHERE PHONE: LIKE '%5551234%' AND do_not_call = false ``` **2. Contacts that opted into SMS this month:** ```sql SELECT name, phone FROM contact WHERE sms_opt_in = true AND created_date = this_month ``` ### Companies (`company`) **1. Large tech companies added recently:** ```sql SELECT name, industry, employees FROM company WHERE industry = 'Technology' AND employees >= 1000 AND created_date = last_quarter ``` **2. Companies missing billing addresses (Null checks):** ```sql SELECT name, website FROM company WHERE billing_address IS NULL AND annual_revenue > 1000000 ``` ### Notes (`note`) **1. Notes created by me on specific days of the week:** ```sql SELECT name, plain_notes, related_to FROM note WHERE created_by = ME AND created_date = last_n_fridays:4 ``` ### Advanced Joins **1. Fetching Leads alongside their related Company data:** ```sql SELECT lead.name, lead.status, company.name, company.industry FROM lead JOIN company ON lead.company = company.id WHERE lead.status = 'Preapproved' AND company.annual_revenue > 500000 ``` --- # 🔍 Valstorm Query Engine Cheat Sheet ## 1. The Basics Your queries follow standard SQL syntax but are supercharged for your CRM data. * **Format:** `SELECT [fields] FROM [object] WHERE [conditions] ORDER BY [field] LIMIT [number]` * **Select All:** Use `*` to get all fields, or `table.*` when doing joins. * **Null Checks:** Use `IS NULL` or `IS NOT NULL`. * **Grouping:** Use parentheses `()` to group `AND` / `OR` logic. ## 2. Magic Filters Use these special keywords in your `WHERE` clause to instantly filter complex data. | Keyword | What It Does | Example | | --- | --- | --- | | **`ME`** | Automatically filters for records owned by or related to your logged-in user account. | `WHERE owner = ME` | | **`PHONE:`** | Searches *all* phone fields on an object simultaneously. Supports `=` and `LIKE`. | `WHERE PHONE: LIKE '%5551234%'` | | **Lookups** | No need to add `.id` for lookups. The engine resolves them automatically. | `WHERE company = '123'` | ## 3. Dynamic Date & Time Keywords Never hardcode a date again. Use these exact keywords with an equals sign (`=`) to automatically filter by time ranges. ### Relative Ranges (Based on right now) * **Days:** `today`, `yesterday`, `tomorrow`, `this_day`, `last_day` * **Weeks:** `this_week`, `last_week`, `next_week` * **Months:** `this_month`, `last_month`, `next_month` * **Quarters:** `last_quarter`, `next_quarter` * **Years:** `this_year`, `last_year`, `next_year` * **Blocks:** `last_7_days`, `last_30_days`, `last_90_days`, `next_7_days`, `next_30_days`, `next_90_days` ### Number-Based Ranges (Replace N with a number) * **Days/Weeks:** `last_n_days:14`, `next_n_weeks:2` * **Months/Years:** `last_n_months:6`, `next_n_years:1` * **Granular:** `last_n_hours:24`, `last_n_minutes:30` * **Specific Days:** `last_n_mondays:3`, `next_n_fridays:2` *(Works for all days)* ### Contextual Ranges (Pass a specific date) * `month_of:'2024-05-15'` *(Finds the whole month of May)* * `week_of:'2024-05-15'` *(Finds the specific week)* * `year_of:'2024-05-15'` *(Finds the specific year)* --- ## 4. Copy & Paste Examples **My Recent High-Value Leads** ```sql SELECT name, status, loan_amount FROM lead WHERE owner = ME AND created_date = this_week AND loan_amount > 500000 ORDER BY loan_amount DESC ``` **Contacts Who Opted into SMS This Month** ```sql SELECT first_name, last_name, phone FROM contact WHERE sms_opt_in = true AND created_date = this_month ``` **Search for a Phone Number Everywhere** ```sql SELECT name, email FROM contact WHERE PHONE: LIKE '%5551234%' ``` **Complex Pipeline Filter** ```sql SELECT * FROM lead WHERE (status = 'New' OR status_reason = 'Needs Follow Up') AND lead_source = 'Website' AND created_date = last_n_days:14 ``` **Join Leads with Company Data** ```sql SELECT lead.name, lead.status, company.name, company.industry FROM lead JOIN company ON lead.company = company.id WHERE lead.status = 'Preapproved' ``` --- ## Why This Structure Works: Designing for People and AI A company's file system is no longer just a digital filing cabinet for people; it is the contextual brain for your AI agents. This structure is deliberately designed to make finding relevant information effortless for both. ### The "Canon" vs. "WIP" Principle One of the biggest challenges with AI (and human onboarding) is outdated, speculative, or half-baked information. If an AI reads a brainstorm document from two years ago, it might hallucinate incorrect company policies. This folder structure solves this by isolating work states: * **The Canon (`_index.md`, `01_Team_Wiki`, `04_Data_and_Reports`):** These folders act as the single source of truth. AI agents are built to weigh documents in these paths heavily. This is the finalized, approved, and "canon" knowledge base of the company. * **The Sandbox (`02_Drafts_and_WIP`, `03_Meeting_Notes`):** These folders contain active, ongoing work. Because of their naming, AI systems know to exclude these folders from standard knowledge-retrieval queries to prevent unfinished ideas from polluting factual answers (unless you explicitly ask the AI to summarize a recent meeting). ### Naming Conventions Consistent naming is critical. It prevents duplicate folders (e.g., `Marketing` vs `Marketing_Team`) and helps algorithms accurately parse context from the file path. Here are three recommended approaches you can adapt for your files and folders: **Option 1: The Ordered Prefix (Recommended for Folders)** * *Format:* `[Number]_[Category]_[Name]` (e.g., `01_Company_Hub`, `02_Marketing`) * *Why use it:* It forces your operating system and web UI to sort folders in priority order, rather than alphabetically. It guides the user’s eye exactly where you want it. **Option 2: The Date-Driven Log (Best for Notes & Reports)** * *Format:* `[YYYY-MM-DD]-[Topic]` (e.g., `2024-10-24-Q3-Review.md`) * *Why use it:* Essential for folders like `03_Meeting_Notes`. It ensures documents sort chronologically and gives AI exact temporal context without needing to read file metadata. **Option 3: Kebab-Case (Best for Developer-Heavy Teams)** * *Format:* `lowercase-with-dashes` (e.g., `brand-assets`, `team-wiki`) * *Why use it:* Clean, URL-friendly, and standard in software engineering. Good if your file system directly maps to a web portal or repository. ## Valstorm Folder Structure ```text Root ├── 01_Company_Hub │ ├── _index.md │ ├── 01_Vision_and_Strategy │ ├── 02_Culture_and_HR │ ├── 03_Brand_and_Assets │ ├── 04_Templates │ ├── 05_Announcements │ └── 06_IT_and_Tools ├── 02_Departments │ ├── 02_Marketing │ │ ├── 01_Team_Wiki │ │ ├── 02_Drafts_and_WIP │ │ ├── 03_Meeting_Notes │ │ └── 04_Data_and_Reports │ ├── 01_Sales │ │ ├── _index.md │ │ ├── 01_Team_Wiki │ │ ├── 02_Drafts_and_WIP │ │ ├── 03_Meeting_Notes │ │ └── 04_Data_and_Reports │ ├── 03_Engineering │ │ ├── _index.md │ │ ├── 01_Team_Wiki │ │ ├── 02_Drafts_and_WIP │ │ ├── 03_Meeting_Notes │ │ └── 04_Data_and_Reports │ ├── 04_Service │ │ ├── _index.md │ │ ├── 01_Team_Wiki │ │ ├── 02_Drafts_and_WIP │ │ ├── 03_Meeting_Notes │ │ └── 04_Data_and_Reports │ └── 05_Finance │ ├── _index.md │ ├── 01_Team_Wiki │ ├── 02_Drafts_and_WIP │ ├── 03_Meeting_Notes │ └── 04_Data_and_Reports │ └── 01_Bank_Transactions ├── 03_Cross_Functional_Projects │ ├── 01_Clients │ │ ├── Client_Alpha │ │ └── Client_Beta │ │ └── 01_Data_Migration ├── 00_John_Doe │ ├── Partner_Testing │ └── local test ├── 00_jane_doe └── 01_John_and_Jane ``` --- ## Visual Folder Structure To illustrate the hierarchy without overwhelming the viewer, this chart shows the top-level structure and expands the **Sales** department as a representative example of the standard folder template. ```mermaid graph LR %% Root Root[Root] %% Level 1 Root --> Hub[01_Company_Hub] Root --> Depts[02_Departments] Root --> CrossFunc[03_Cross_Functional_Projects] Root --> Users[User Workspaces] %% 01_Company_Hub (Summary) Hub -.-> HubDocs[Company-wide Docs, Assets, HR] %% 02_Departments (Show Sales as standard template) Depts --> Dept_Mktg[02_Marketing] Depts --> Dept_Sales[01_Sales] Depts --> Dept_Eng[03_Engineering] Depts --> Dept_Service[04_Service] Depts --> Dept_Finance[05_Finance] %% Expand Sales as Template Dept_Sales --> Sales_Wiki[01_Team_Wiki] Dept_Sales --> Sales_Drafts[02_Drafts_and_WIP] Dept_Sales --> Sales_Notes[03_Meeting_Notes] Dept_Sales --> Sales_Data[04_Data_and_Reports] %% Cross Functional CrossFunc --> Proj_Clients[01_Clients] Proj_Clients --> Client_A[Client_Alpha] Proj_Clients --> Client_B[Client_Beta] %% Users Users --> John[00_John_Doe] Users --> Jane[00_jane_doe] ``` --- --- Validation rules in ValStorm allow you to enforce data integrity by preventing users (or automations) from saving records that don't meet your business criteria. ## 📋 How It Works A validation rule uses the **Formula Engine** to evaluate a specific expression every time a record is created or updated. 1. **Trigger**: Validation occurs during the record save process, *after* formula fields are calculated but *before* the data is committed to the database. 2. **Evaluation**: The rule's expression is calculated. 3. **Outcome**: * If the expression returns **TRUE**: The validation passes, and the save continues. * If the expression returns **FALSE** (or an error): The validation fails. The save is blocked, and the custom error message is displayed to the user. --- ## 🏗️ Components of a Rule | Component | Description | | :--- | :--- | | **Name** | A human-readable label for the rule (e.g., "Minimum Age Requirement"). | | **API Name** | A unique identifier used by the system. | | **Rule Expression** | The formula that must evaluate to **TRUE** for the data to be valid. | | **Error Message** | The message shown to the user if the rule fails. This can be a static string or a dynamic formula. | --- ## 💡 Best Practices ### 1. The "Pass" Condition Always write your expression as the condition that makes the data **valid**. * **Correct**: `{{record.amount}} > 0` (The record is valid if amount is positive). * **Incorrect**: `{{record.amount}} < 0` (This would block all positive amounts!). ### 2. Handle Blanks Use `ISBLANK()` to ensure you aren't running calculations on missing data that might cause a rule to fail unexpectedly. * **Example**: `IF(ISBLANK({{record.email}}), TRUE, CONTAINS({{record.email}}, "@"))` ### 3. Descriptive Error Messages Provide clear instructions on how to fix the error. Use dynamic formulas in error messages to show the user exactly what is wrong. * **Static**: "The discount is too high." * **Dynamic**: `'Discount of ' & {{record.discount_pct}} & '% exceeds the limit of 20%.'` --- ## 🚀 Common Examples ### Require a field based on another field If the Lead Source is "Referral", the "Referred By" field must not be empty. * **Expression**: `IF({{record.lead_source}} == 'Referral', NOT(ISBLANK({{record.referred_by}})), TRUE)` * **Error**: "Referred By is required when Lead Source is Referral." ### Prevent future dates Ensure the "Birthdate" field is not set in the future. * **Expression**: `{{record.birthdate}} <= TODAY()` * **Error**: "Birthdate cannot be in the future." ### Value ranges Ensure a "Probability" field is between 0 and 100. * **Expression**: `{{record.probability}} >= 0 && {{record.probability}} <= 100` * **Error**: "Probability must be between 0 and 100." ### Pattern enforcement Ensure a code starts with "ACT-". * **Expression**: `LEFT({{record.account_code}}, 4) == "ACT-"` * **Error**: "Account codes must start with the prefix 'ACT-'." --- ## 🛠️ Implementation via UI 1. Navigate to the **Object Builder**. 2. Select your Object. 3. Click the **Validation Rules** tab in the sidebar. 4. Click **New Validation Rule**. 5. Use the **Formula Editor** (with autocompletion) to build your expression and error message. 6. Click **Save Validation Rule**. --- This guide provides an in-depth explanation of the various field types available in the ValStorm dynamic schema system. ValStorm uses a JSON-Schema inspired structure to define object properties, with custom extensions for business logic, UI rendering, and multi-tenancy. ## 📋 Core Field Properties Every field definition in ValStorm can include several standard properties: | Property | Type | Description | | :------------ | :-------- | :------------------------------------------------------------------------------ | | `api_name` | `string` | The unique identifier for the field in API calls and database queries. | | `title` | `string` | The display label shown in the UI. | | `type` | `string` | The underlying data type (e.g., `string`, `number`, `boolean`, `json`, `list`). | | `format` | `string` | Hints the UI on how to render the field (e.g., `date`, `email`, `lookup`). | | `anyOf` | `array` | Used primarily to allow null values: `[{"type": "string"}, {"type": "null"}]`. | | `required` | `array` | Defined at the **object level**, listing the `api_name` of mandatory fields. | | `pii` / `phi` | `boolean` | Flag for Personally Identifiable Information or Protected Health Information. | | `description` | `string` | Internal documentation for developers. | | `help_text` | `string` | Tooltip text shown to end-users in the UI. | *** ## Text & Input ### 1. Basic Text Used for short strings like names, titles, or single-line inputs. * **Type:** `string` * **Format:** `null` (default) * **Example:** `text_field_required` (no `anyOf`) or `text_field_not_required` (with `anyOf` including `null`). ### 2. Text Area Used for multi-line text input. * **Type:** `string` * **Format:** `text-area` ### 3. Rich Content (HTML, Markdown, Rich Text) Specialized formats for formatted content. * **Type:** `string` * **Formats:** `html`, `markdown`, `rich-text` *** ## 🔢 Numeric & Financial ### 1. Number Standard integer or floating-point numbers. * **Type:** `number` ### 2. Currency Formats the number as a currency value in the UI. * **Type:** `number` * **Format:** `currency` ### 3. Percent Renders the number as a percentage. * **Type:** `number` * **Format:** `percent` *** ## 🔘 Selection & Picklists ### 1. Enum A fixed set of strings defined directly in the schema. * **Type:** `string` * **Format:** `enum` * **Property:** `enum: ["Option A", "Option B"]` ### 2. Picklist (Standard) A dropdown menu. ValStorm supports three variations: * **Global:** Uses a tenant-wide shared list (e.g., `global_list_name: "Lead Source"`). * **Restricted:** Only allows values defined in the API metadata. * **Dependent:** Values change based on another field's selection (logic typically driven by tags). ### 3. Multi-Select (Multiple Dropdown) Allows selecting multiple options from a list. * **Type:** `list` * **Format:** `multi-select` *** ## 🔗 Relationships & Lookups\` Lookups are the "foreign keys" of ValStorm, connecting records across different collections. ### 1. Lookup A single reference to another record. * **Type:** `json` * **Format:** `lookup` * **Property:** `schema: "target_object_api_name"` ### 2. Lookup List A reference to multiple records from a specific collection. * **Type:** `list` * **Format:** `lookup_list` * **Property:** `schema: "target_object_api_name"` ### 3. Compound Lookup A flexible reference that can point to records in *any* collection. * **Type:** `json` * **Format:** `compound_lookup` *** ## 📅 Date & Time ### 1. Date A standard date (YYYY-MM-DD). * **Type:** `string` * **Format:** `date` ### 2. Date Time A full timestamp. * **Type:** `string` * **Format:** `date-time` ### 3. Date List An array of date strings. * **Type:** `list` * **Format:** `date_list` ### 4. Timezone A string representing a standard IANA timezone identifier (e.g., `America/New_York`). Rendered as a friendly dropdown with UTC offsets. * **Type:** `string` * **Format:** `timezone` *** ## 🧪 Logic & Formulas ### 1. Formula Field Formula fields are read-only, **virtual** fields that calculate their values on-the-fly whenever a record is fetched. They are not persisted in the database. * **Identification:** `is_formula: true` * **Syntax:** * Uses Mustache-style syntax for field references: `{{record.field_api_name}}`. * Supports standard operators (`AND`, `OR`, `=`) as well as JS-style equivalents (`&&`, `||`, `==`). * Keywords and logical operators are case-insensitive (`TRUE`, `true`, `and`, `AND`). * **Properties:** * `formula`: The calculation string (e.g., `{{record.first_name}} & ' ' & {{record.last_name}}`). * `result_type`: Determines the final data type and formatting (`string`, `number`, `boolean`, `date`, `datetime`, `currency`, `percent`). * `virtual`: Always `true`. ### 2. Rollup Summary Field Rollup summary fields are read-only, **virtual** fields that calculate aggregate values from related child records in real-time. * **Identification:** `is_rollup: true` * **Properties:** * `rollup_target_object`: The API name of the child object to aggregate from (e.g., `quote_line_item`). * `rollup_relationship_field`: The API name of the lookup field on the child object that points back to the current object. * `rollup_type`: The aggregation method (`count`, `sum`, `avg`, `min`, `max`). * `rollup_source_field`: The field on the child record to perform the calculation on (required for all types except `count`). * `result_type`: Controls the final formatting. Usually inherits from the source field (e.g., `currency`). * `virtual`: Always `true`. *** ## 🛠️ Specialized Types ### 1. Phone Stores structured phone data. * **Type:** `json` * **Format:** `json` (handled via `type: phone` in UI) * **Default Structure:** `{"friendly_number": "", "country_code": "", "phone_number": "", "extension": ""}` ### 2. Address Stores geo-location and address components. * **Type:** `object` * **Format:** `address` ### 3. Image Handles file uploads for images. * **Type:** `object` * **Format:** `image` ### 4. Boolean A simple true/false toggle. * **Type:** `boolean` ### 5. Color & Icon UI-specific fields for selecting hex codes or system icons. * **Type:** `string` * **Formats:** `color`, `icon` *** ## 🧬 System & Advanced ### 1. Availability / User Availability Used for scheduling and agent presence logic. * **Type:** `json` * **Formats:** `availability`, `user_availability` ### 2. Raw JSON / List For storing unstructured or complex nested data that doesn't fit standard types. * **Types:** `json`, `list` * **Format:** `json` (for the JSON type) ## Comprehensive JSON View Below is a showing of all of the possible field types in side a JSON object. This is mainly for inspection by developers to understand the underlying data model. ```json { "app": "", "description": "", "exclusive_ownership": false, "junction_object": false, "ownership": false, "properties": { "api_name": { "title": "Api Name", "type": "string", "api_name": "api_name" }, "text_field_not_required": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Text Field Not Requireds", "title": "Text Field Not Required", "custom": false, "api_name": "text_field_not_required" }, "text_field_required": { "type": "string", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Text Field Requireds", "title": "Text Field Required", "custom": false, "api_name": "text_field_required" }, "text_area_not_required": { "format": "text-area", "type": "string", "anyOf": [ { "type": "string" }, { "type": "null" } ], "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Text Area Not Requireds", "title": "Text Area Not Required", "custom": false, "api_name": "text_area_not_required" }, "text_area_required": { "format": "text-area", "type": "string", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Text Area Requireds", "title": "Text Area Required", "custom": false, "api_name": "text_area_required" }, "picklist_global": { "restricted": false, "global": true, "global_list_name": "Lead Source", "schema": null, "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "picklist", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Picklist Globals", "title": "Picklist Global", "custom": false, "api_name": "picklist_global" }, "picklist_restricted_to_api_values": { "restricted": true, "global": false, "global_list_name": null, "schema": null, "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "picklist", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Picklist Restricted to API Valuess", "title": "Picklist Restricted to API Values", "custom": false, "api_name": "picklist_restricted_to_api_values" }, "picklist_dependent": { "restricted": false, "global": false, "global_list_name": null, "schema": null, "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "picklist", "app": "", "pii": false, "phi": false, "description": "Pick list with dependencies that are generated by tags", "help_text": "Pick list with dependencies that are generated by tags", "plural_name": "Picklist Dependents", "title": "Picklist Dependent", "custom": false, "api_name": "picklist_dependent" }, "multiple_dropdown": { "restricted": false, "global": false, "global_list_name": null, "schema": null, "format": "multi-select", "anyOf": [ { "type": "list" }, { "type": "null" } ], "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Multiple Dropdowns", "title": "Multiple Dropdown", "custom": false, "api_name": "multiple_dropdown" }, "enum_field": { "enum": [ "One", "Two", "Three" ], "anyOf": [ { "enum": [ "One", "Two", "Three" ], "type": "string" }, { "type": "null" } ], "format": "enum", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Enum Fields", "title": "Enum Field", "custom": false, "api_name": "enum_field" }, "phone": { "anyOf": [ { "type": "phone" }, { "type": "null" } ], "format": "json", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "PHones", "default": { "friendly_number": "", "country_code": "", "phone_number": "", "extension": "" }, "title": "PHone", "custom": false, "api_name": "phone" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "email", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Emails", "title": "Email", "custom": false, "api_name": "email" }, "number": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Numbers", "title": "Number", "format": "", "custom": false, "api_name": "number" }, "currency": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "format": "currency", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Currencys", "title": "Currency", "custom": false, "api_name": "currency" }, "percent": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "format": "percent", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Percents", "title": "Percent", "custom": false, "api_name": "percent" }, "image": { "anyOf": [ { "type": "object" }, { "type": "null" } ], "format": "image", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Images", "title": "Image", "custom": false, "api_name": "image" }, "address": { "anyOf": [ { "type": "object" }, { "type": "null" } ], "format": "address", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Addresss", "title": "Address", "custom": false, "api_name": "address" }, "date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "date", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Dates", "title": "Date", "custom": false, "api_name": "date" }, "date_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "date-time", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Date Times", "title": "Date Time", "custom": false, "api_name": "date_time" }, "boolean": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Booleans", "title": "Boolean", "format": "", "custom": false, "api_name": "boolean" }, "json": { "default": null, "format": "json", "type": "json", "anyOf": [ { "type": "json" }, { "type": "null" } ], "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "JSONs", "title": "JSON", "custom": false, "api_name": "json" }, "list": { "anyOf": [ { "type": "list" }, { "type": "null" } ], "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Lists", "title": "List", "format": "", "custom": false, "api_name": "list" }, "link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "link", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Links", "title": "Link", "custom": false, "api_name": "link" }, "html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "html", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Htmls", "title": "Html", "custom": false, "api_name": "html" }, "rich_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "rich-text", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Rich Texts", "title": "Rich Text", "custom": false, "api_name": "rich_text" }, "markdown": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "markdown", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Markdowns", "title": "Markdown", "custom": false, "api_name": "markdown" }, "availability": { "anyOf": [ { "type": "json" }, { "type": "null" } ], "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Availabilitys", "title": "Availability", "format": "availability", "custom": false, "api_name": "availability" }, "user_availability": { "anyOf": [ { "type": "json" }, { "type": "null" } ], "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "User Availabilitys", "title": "User Availability", "format": "user_availability", "custom": false, "api_name": "user_availability" }, "color": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "color", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Colors", "title": "Color", "custom": false, "api_name": "color" }, "icon": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "icon", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Icons", "title": "Icon", "custom": false, "api_name": "icon" }, "date_list": { "anyOf": [ { "type": "list" }, { "type": "null" } ], "format": "date_list", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Date Lists", "title": "Date List", "custom": false, "api_name": "date_list" }, "lookup": { "anyOf": [ { "type": "json" }, { "type": "null" } ], "format": "lookup", "schema": "object_api_name", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Lookups", "title": "Lookup", "custom": false, "api_name": "lookup" }, "lookup_list": { "anyOf": [ { "type": "list" }, { "type": "null" } ], "format": "lookup_list", "schema": "object_api_name", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Lookup Lists", "title": "Lookup List", "custom": false, "api_name": "lookup_list" }, "compound_lookup": { "anyOf": [ { "type": "json" }, { "type": "null" } ], "format": "compound_lookup", "app": "", "pii": false, "phi": false, "description": "", "help_text": "", "plural_name": "Compound Lookups", "title": "Compound Lookup", "custom": false, "api_name": "compound_lookup" }, "next_birthday_formula": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "format": "date", "app": "b743cbd7-45db-4f5a-b0e9-a503c08f86e5", "pii": false, "phi": false, "description": "Calculates the next occurrence of the birthday", "help_text": "", "plural_name": "Next Birthdays", "title": "Next Birthday", "is_formula": true, "formula": "NEXT_BIRTHDAY({{record.birthday}})", "result_type": "date", "virtual": true, "custom": true, "api_name": "next_birthday_formula" }, "total_quote_amount_rollup": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "format": "currency", "app": "6eb51491-d7a8-43ea-971d-d4d5d7464043", "pii": false, "phi": false, "description": "Calculates the total amount of all related line items", "help_text": "", "plural_name": "Total Quote Amounts", "title": "Total Quote Amount", "is_rollup": true, "rollup_target_object": "quote_line_item", "rollup_relationship_field": "quote", "rollup_type": "sum", "rollup_source_field": "amount", "result_type": "currency", "virtual": true, "custom": true, "api_name": "total_quote_amount_rollup" } }, "relates_to_any_object": false, "required": [ "text_field_required", "text_area_required", "api_name" ], "title": "Testing", "type": "object", "id": "a037f889-49fb-458b-9872-33b33c9b4e0b", "api_name": "testing", "custom": false } ```