You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Worker: Sidekiq standalone (./worker) - API-only communication
Business: Git submodule (./extensions/business) - proprietary features (billing, BaaS, reseller, AI publisher)
System: Git submodule (./extensions/system) - node lifecycle, module CRUD, fleet autonomy, on-node Go agent, initramfs, CLI. Public on GitHub (MIT) at rett/powernode-system, private on Gitea. Mounted as a submodule of this repo since 2026-05-02.
Database: PostgreSQL with native UUID schema
Payments: Stripe, PayPal with PCI compliance (business only)
Project Status: See docs/TODO.md (auto-generated from shared knowledge — do not edit manually)
Core Models
Account → User (many), Subscription (one)
Subscription → Plan, Payments, Invoices
User → Roles, Permissions, Invitations
Specialists
Use platform.discover_skills with a task description to find the right specialist capability. Fallback: MCP_CONFIGURATION.md.
Staged commits: Group changes into logical commits by concern (models, services, controllers, frontend, tests, config) — never one monolithic commit
Business Submodule (./extensions/business)
Separate git repo at extensions/business/ — has its own branch, commits, and remote (git@git.ipnode.net:powernode/powernode-business.git)
Always check both repos: git status in root AND git -C extensions/business status — changes in extensions/business/ are invisible to the parent repo's git status
Commit order: Commit inside extensions/business/ first, then update the submodule pointer in the parent repo
Path aliases: Business frontend uses @business/ for intra-business imports, @/ for core shared imports
Core mode: When business submodule is absent, the app runs as single-user self-hosted (all features unlocked, no billing/SaaS)
Feature gating: Shared::FeatureGateService.business_loaded? (backend), __BUSINESS__ build flag (frontend), businessOnly: true on nav items
Permission-Based Access Control (CRITICAL)
Frontend MUST use permissions ONLY - NEVER roles for access control
Backend: Use current_user.has_permission?('name') - NEVER permissions.include?() (returns objects)
Frontend Patterns
Pattern
Rule
Colors
Theme classes only: bg-theme-*, text-theme-*
Navigation
Flat structure - no submenus
Actions
ALL in PageContainer - none in page content
State
Global notifications only - no local success/error
Imports
Path aliases for cross-feature: @/shared/, @/features/
Logging
No console.log in production — use import { logger } from '@/shared/utils/logger' instead
Types
No any - proper TypeScript types required
Backend Patterns
Pattern
Rule
Controllers
Api::V1 namespace, inherit ApplicationController
Responses
MANDATORY: render_success(), render_error()
Worker Jobs
Inherit BaseJob, use execute() method, API-only
Ruby Files
# frozen_string_literal: true pragma required
Logging
Rails.logger - no puts/print
Migrations
t.references automatically creates an index — NEVER use add_index for reference columns. Customize via the declaration itself: t.references :account, index: { unique: true }
Namespaces
ALL namespaced models MUST use :: separator in class_name: — e.g., Ai::AgentTeam not AiAgentTeam, Devops::Pipeline not DevopsPipeline, BaaS::Tenant not BaaSTenant
Seeds
After modifying seeds, run cd server && rails db:seed and verify completion
Associations
Always pair class_name: with foreign_key: — e.g. belongs_to :provider, class_name: "Ai::Provider", foreign_key: "ai_provider_id"
Foreign Keys
Namespaced FK prefixes: Ai:: → ai_ (ai_agent_id), Devops:: → devops_ (devops_pipeline_id), BaaS:: → baas_ (baas_customer_id). Others: use explicit FK or omit if unambiguous
JSON Columns
Always use lambda defaults: attribute :config, :json, default: -> { {} } — never default: {}
Controller Size
Controllers MUST stay under 300 lines — extract query logic to services, serialization to concerns
Eager Loading
Always use .includes() when iterating associations — never bare .all followed by .map/.each accessing relations
Webhook Receivers
Inbound webhooks MUST return 200/202 on processing errors — NEVER 500 (causes provider retry storms)
Cryptographic Material Safety (ABSOLUTE RULES)
Rule
Details
No key output
NEVER output, log, display, echo, or transmit private keys, API secrets, seed phrases, mnemonics, or signing material in any form
No keys in code
NEVER store keys, secrets, or credentials in source code files, scripts, configs, environment files, or documentation
No CLI key generation
NEVER generate private keys via CLI commands (rails runner, rake, irb) where they could appear in shell history
Vault-only storage
ALL key generation MUST happen inside Vault or WalletKeyService (which stores directly to Vault)
Audit all key ops
ALL key operations (generate, import, revoke, sign) MUST be logged to Trading::AuditLog
No key arguments in logs
NEVER pass private keys as function arguments that could appear in logs, error messages, or exception traces
Guide, don't handle
When assisting with wallet setup, guide the user through the UI/API — never handle key material directly
Design Principles
Principle
Rule
Reuse First
platform.discover_skills + platform.search_knowledge + platform.code_semantic_search before proposing anything new — never standalone/greenfield when infrastructure exists
Quality Gates
Run cd frontend && npx tsc --noEmit after TS changes, verify Ruby syntax after .rb changes
Verify Seeds
After seed modifications: cd server && rails db:seed — watch for association/validation errors
Stop & Ask
HARD RULE: After 3 failed attempts at the same fix, STOP immediately and ask the user. Do NOT try a 4th approach, do NOT continue iterating, do NOT try workarounds. Present what you tried and ask for guidance
Audit Sessions
When asked to audit/review/analyze code, save findings to docs/ and do NOT implement changes. Audit = report only, unless the user explicitly says to fix
Verify Changes
Ruby: syntax check + related spec. TypeScript: tsc --noEmit. Migrations: rails db:migrate:status. Seeds: rails db:seed. Use /verify for targeted checks
Verify CWD
Before git operations on submodules, always git rev-parse --show-toplevel to confirm you're in the right repo
Completion Gate
Before reporting work as done, run /verify on changed files. Never mark a task complete with unverified changes
Dead Reference Cleanup
After deleting any file, grep -r for all import/require references to it across the codebase and remove them before committing
Plan Before Multi-File
Changes touching 3+ files: outline which files will change and data flow direction, then wait for user approval before writing code. Single-file fixes can proceed directly
Parallel Investigation
When debugging spans backend + frontend or 3+ services, spawn parallel sub-agents: one per layer/service. Merge findings before proposing a fix — never serialize investigation across layers
Architecture Principles
Principle
Rule
Pull, Never Push
Downstream managers always pull from upstream sources — upstream services NEVER push to downstream. When unsure about data flow direction, ask before implementing
Extension Isolation
Each extension (extensions/*) is self-contained. Extensions depend on core, core NEVER depends on extensions
Service Boundaries
Cross-namespace communication goes through service interfaces, never direct model access across namespaces
Bulk Operation Safety
Rule
Details
State the count
Before ANY bulk operation (approve, reject, delete, update), always state the exact count: "This will affect N items"
Confirmation threshold
Operations affecting more than 5 items require explicit user confirmation
Show samples
For bulk operations, show the first 3 and last 1 items for verification
Never batch-approve
Training decisions, permission grants, and financial operations MUST be reviewed individually
System extension is dual-remoted: origin = git@git.ipnode.org:powernode/powernode-system.git (private), github = git@github.com:rett/powernode-system.git (public, MIT-licensed) — the only extension whose code is public; commits get pushed to both
CWD verification: Before EVERY git add/git commit, run git rev-parse --show-toplevel and verify it matches the intended repo
Never commit extension files from parent: Files under extensions/*/ MUST be committed from within the submodule. Running git add extensions/trading/... from parent only stages a pointer change
Commit order: Commit inside each submodule FIRST, then update pointers in parent
Terminology
Term
Meaning
Don't Confuse With
server/
Rails app directory on disk
Not "backend directory"
powernode-backend
Systemd service name (powernode-backend@default)
Not "server service" or "rails service"
worker/
Standalone Sidekiq app directory
Not "job runner"
powernode-worker
Systemd service name (powernode-worker@default)
Not "sidekiq service"
Service Management
# Systemd services (requires initial install: sudo scripts/systemd/powernode-installer.sh install)
sudo systemctl start powernode.target # Start all services
sudo systemctl stop powernode.target # Stop all services
sudo systemctl restart powernode-backend@default # Restart individual service
sudo scripts/systemd/powernode-installer.sh status # Show all service status
journalctl -u powernode-backend@default -f # Tail service logs
NEVER use manual commands (rails server, sidekiq, npm start)
Service Operations Reference
Service
Unit Name
Port
Restart Behavior
Rails API
powernode-backend@default
3000
SIGUSR2 reload (~30ms) via scripts/reload-backend.sh. Auto-reloaded by Stop hook after .rb edits
Sidekiq
powernode-worker@default
—
Full restart (~28s drain). Wait 30s before checking status — "deactivating" is normal during drain
Worker HTTP API
powernode-worker-web@default
4567
If port 4567 refused, restart THIS service, not powernode-worker
Frontend
powernode-frontend@default
5173
Full restart
Stuck worker: If worker is draining >30s, use sudo systemctl stop powernode-worker@default && sudo systemctl start powernode-worker@default (stop+start, not restart)
Never restart worker multiple times in quick succession — batch code changes, ONE restart at end
After restart: Verify with sudo scripts/systemd/powernode-installer.sh status
Test Execution
RSpec:
cd server && bundle exec rspec --format progress # Full suitecd server && bundle exec rspec spec/path_spec.rb # Single file
Frontend tests - always use CI=true:
cd frontend && CI=true npm test
Multi-Agent Test Rules
Uses DatabaseCleaner with :deletion strategy — avoids TRUNCATE deadlocks between concurrent processes.
Do NOT run multiple single-process rspec instances simultaneously on the same database.
Frontend tests (CI=true npm test) and TypeScript checks (npx tsc --noEmit) are always safe to run concurrently.
Worker Architecture (CRITICAL)
The server (server/) is a Rails API — it does NOT run Sidekiq
The worker (worker/) is a standalone Sidekiq process — it communicates with server via HTTP API only
NEVER create job classes in server/app/jobs/ — jobs belong in worker/app/jobs/
NEVER add Sidekiq gems to server/Gemfile
NEVER modify worker/ files when fixing server issues
Test Patterns Reference
Pattern
Rule
Factories
spec/factories/ — use existing factories with traits (:active, :paused, :archived). AI factories in spec/factories/ai/
User Setup
user_with_permissions('perm.name') from permission_test_helpers.rb — never create users manually
Auth Headers
auth_headers_for(user) returns { Authorization: Bearer ... } — use in all request specs
The Powernode MCP server (platform.* tools) is the primary knowledge source. File scanning is the fallback. MCP queries are NOT optional — they are mandatory protocol steps.
SESSION START Protocol (MANDATORY — every session)
Run platform.knowledge_health — establish baseline, identify stale/conflicting knowledge
Run platform.learning_metrics — check active learnings count, recent contributions
If stale_count > 0 or conflicts detected, note them for resolution during the session
Run platform.code_index_status with repository_id: "powernode-platform" — check codebase index freshness and stale file count
BEFORE EVERY CODE CHANGE (MANDATORY)
Search existing knowledge for the area being modified — not optional, not "when convenient":
platform.query_learnings — established patterns, anti-patterns, failure modes for this area
platform.search_knowledge — procedures, code snippets, reference material
Self-check: "Did I create learnings for the critical findings in this task?" If no, do it now.
Skip Contributions For
Trivial fixes (typos, simple renames, formatting)
Speculative or unverified analysis
Knowledge that already exists in MCP (always search first)
MCP Helper (Claude Code Sessions)
Claude Code can invoke MCP tools via the Powernode MCP endpoint. The workspace SSE daemon (/.claude/hooks/workspace-sse-daemon.sh) manages OAuth tokens and sessions. Helper functions are available via source .claude/hooks/mcp-helper.sh:
Before starting work: Run platform.knowledge_health if last check was >24h ago
When encountering bugs: Always search platform.query_learnings for existing fix — if found, reinforce_learning; if not, fix and create_learning
When removing stale code: Create a learning documenting what was removed and why
When fixing documentation: Update platform.update_knowledge to correct the source entry
Tool Evolution
All platform.* tools are defined in server/app/services/ai/tools/platform_api_tool_registry.rb.
When tools are added/modified, run cd server && rails mcp:generate_tool_catalog to regenerate docs/platform/MCP_TOOL_CATALOG.md.
When MCP knowledge is updated significantly, run cd server && rails mcp:sync_docs to regenerate fallback docs in docs/platform/knowledge/.
Knowledge sync runs automatically daily at 5:30 AM UTC via AiKnowledgeDocSyncJob.
Adding a New Tool
Create tool class in server/app/services/ai/tools/
Add action→class mapping to PlatformApiToolRegistry::TOOLS
Add action_definitions with descriptions and parameter schemas
Run rails mcp:generate_tool_catalog → updates docs/platform/MCP_TOOL_CATALOG.md
Update relevant CLAUDE.md component file(s) with the new tool
Create learning: platform.create_learning category: pattern documenting the new tool
Deprecating a Tool
Add deprecation notice to action_definitions description
Create learning: platform.create_learning category: best_practice documenting the replacement