chore: organize documentation structure for monorepo

- Create /docs structure (microdao, daarion, agents)
- Organize 61 cursor technical docs
- Add README files for each category
- Copy key documents to public categories
- Add GitHub setup instructions and scripts
This commit is contained in:
Apple
2025-11-15 04:08:35 -08:00
parent 5520665600
commit c552199eed
138 changed files with 39624 additions and 40 deletions

15
src/App.tsx Normal file
View File

@@ -0,0 +1,15 @@
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import { OnboardingPage } from './pages/OnboardingPage';
function App() {
return (
<Routes>
<Route path="/onboarding" element={<OnboardingPage />} />
<Route path="/" element={<div>Home - Coming soon</div>} />
</Routes>
);
}
export default App;

View File

@@ -1,51 +1,71 @@
# MicroDAO Frontend - Структура проекту
# MicroDAO Backend — Source Code Structure
## Структура каталогів
Цей документ описує структуру коду backend-частини MicroDAO/DAARION.city.
## Структура папок
```
src/
api/ # API клієнти та типи
client.ts # Базовий API клієнт
auth.ts # Авторизація
teams.ts # Спільноти
channels.ts # Канали
agents.ts # Агенти
components/ # React компоненти
onboarding/ # Компоненти онбордингу
OnboardingStepper.tsx
StepWelcome.tsx
StepCreateTeam.tsx
StepSelectMode.tsx
StepCreateChannel.tsx
StepAgentSettings.tsx
StepInvite.tsx
hooks/ # React hooks
useOnboarding.ts
pages/ # Сторінки
OnboardingPage.tsx
types/ # TypeScript типи
api.ts
├── domain/ # Чисті доменні типи та логіка (без I/O)
├── dao/ # DAO domain types & logic
├── wallet/ # Wallet domain types
├── pdp/ # PDP policy model
└── user/ # User domain types
├── services/ # Бізнес-логіка сервісів
├── wallet/ # Wallet Service
├── dao-factory/ # DAOFactory Service
├── registry/ # Registry Service
│ ├── pdp/ # PDP Service
└── router/ # Router/Agent runtime (майбутнє)
├── api/ # HTTP API layer
├── http/ # Express routes
└── middleware/ # Auth, context middleware
├── infra/ # Інфраструктура
│ ├── db/ # Database access
├── logger/ # Logging
│ └── config/ # Configuration
└── app.ts # Application entry point
```
## Онбординг
## Принципи архітектури
Онбординг реалізовано як багатокроковий процес з 6 кроками:
1. **Domain Layer** (`domain/`) — чисті типи та бізнес-логіка без залежностей від інфраструктури
2. **Services Layer** (`services/`) — реалізація бізнес-логіки згідно `core-services-mvp.md`
3. **API Layer** (`api/`) — HTTP-рівень, що викликає сервіси
4. **Infrastructure Layer** (`infra/`) — БД, логування, конфігурація
1. **Ласкаво просимо** - привітальний екран
2. **Створити спільноту** - форма з назвою та описом
3. **Режим приватності** - вибір Public/Confidential
4. **Перший канал** - створення каналу
5. **Агент та пам'ять** - налаштування агента
6. **Запросити команду** - посилання-запрошення
## Документація
## API Інтеграція
- `docs/core-services-mvp.md` — специфікація core-сервісів
- `docs/api-mvp.md` — API специфікація
- `docs/pdp_access.md` — PDP та система доступів
Всі API виклики типізовані та обробляють помилки. Базовий URL налаштовується через змінну середовища `VITE_API_URL` (за замовчуванням `https://api.microdao.xyz`).
## Запуск
## Наступні кроки
```bash
npm install
npm run dev
```
- Додати сторінку налаштувань (Settings)
- Реалізувати чат інтерфейс
- Додати публічний канал landing page
- Інтегрувати WebSocket для real-time оновлень
## MVP Status
Наразі реалізовано:
- ✅ Структура проекту
- ✅ Domain types
- ✅ Wallet Service (stub)
- ✅ DAOFactory Service
- ✅ Registry Service
- ✅ PDP Service
- ✅ HTTP Routes
- ✅ Middleware
TODO:
- [ ] Інтеграція з реальною БД
- [ ] JWT авторизація
- [ ] On-chain інтеграція для Wallet
- [ ] Agent Runtime
- [ ] Тести

View File

@@ -0,0 +1,53 @@
/**
* Agents Routes (MVP Stub)
* Based on: api-mvp.md
*
* Endpoints:
* - POST /api/v1/dao/{dao_id}/agents/{agent_id}/invoke - Invoke agent
*/
import { Router } from 'express';
import { pdpService } from '../../services/pdp/pdp.service';
export const agentsRoutes = Router();
// POST /api/v1/dao/{dao_id}/agents/{agent_id}/invoke - Invoke agent
agentsRoutes.post('/:daoId/agents/:agentId/invoke', async (req, res) => {
try {
const userId = (req as any).userId;
const { daoId, agentId } = req.params;
const { input, metadata } = req.body;
// Check PDP policy
const pdpResult = await pdpService.check(
'policy.agent.run',
{ agentId },
{ userId, daoId }
);
if (pdpResult.decision !== 'allow') {
res.status(403).json({
error: 'ACCESS_DENIED',
message: pdpResult.reason || 'PDP denied',
});
return;
}
// MVP: Return stub response
// TODO: Implement actual agent invocation
const runId = `run_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
res.json({
run_id: runId,
status: 'queued',
output: null,
});
} catch (error: any) {
res.status(500).json({
error: 'INTERNAL_ERROR',
message: error.message,
});
}
});

View File

@@ -0,0 +1,78 @@
/**
* DAO Routes
* Based on: api-mvp.md
*
* Endpoints:
* - POST /api/v1/dao - Create DAO
* - GET /api/v1/dao/{dao_id} - Get DAO by ID
* - GET /api/v1/dao - List DAOs
*/
import { Router } from 'express';
import { daoFactoryService } from '../../services/dao-factory/dao-factory.service';
import { registryService } from '../../services/registry/registry.service';
import type { CreateDaoInput } from '../../domain/dao/types';
export const daoRoutes = Router();
// POST /api/v1/dao - Create DAO
daoRoutes.post('/', async (req, res) => {
try {
const userId = (req as any).userId;
const input: CreateDaoInput = req.body;
const result = await daoFactoryService.createDao(userId, input);
res.status(201).json(result);
} catch (error: any) {
res.status(400).json({
error: error.message || 'BAD_REQUEST',
message: error.message,
});
}
});
// GET /api/v1/dao/{dao_id} - Get DAO by ID
daoRoutes.get('/:daoId', async (req, res) => {
try {
const { daoId } = req.params;
const dao = await registryService.getDaoById(daoId);
if (!dao) {
res.status(404).json({
error: 'NOT_FOUND',
message: `DAO ${daoId} not found`,
});
return;
}
res.json(dao);
} catch (error: any) {
res.status(500).json({
error: 'INTERNAL_ERROR',
message: error.message,
});
}
});
// GET /api/v1/dao - List DAOs
daoRoutes.get('/', async (req, res) => {
try {
const { level, type } = req.query;
const filter = {
level: level as string | undefined,
type: type as string | undefined,
};
const daos = await registryService.listDaos(filter);
res.json({ items: daos });
} catch (error: any) {
res.status(500).json({
error: 'INTERNAL_ERROR',
message: error.message,
});
}
});

View File

@@ -0,0 +1,38 @@
/**
* PDP Routes
* Based on: api-mvp.md
*
* Endpoints:
* - POST /api/v1/pdp/check - Check policy
*/
import { Router } from 'express';
import { pdpService } from '../../services/pdp/pdp.service';
import type { PdpRequest } from '../../domain/pdp/policy.model';
export const pdpRoutes = Router();
// POST /api/v1/pdp/check - Check policy
pdpRoutes.post('/check', async (req, res) => {
try {
const userId = (req as any).userId;
const { policy, resource, context }: PdpRequest = req.body;
const result = await pdpService.check(policy, resource, {
...context,
userId: context.userId || userId,
});
res.json({
decision: result.decision,
reason: result.reason || null,
});
} catch (error: any) {
res.status(500).json({
error: 'INTERNAL_ERROR',
message: error.message,
});
}
});

View File

@@ -0,0 +1,48 @@
/**
* Platforms Routes
* Based on: api-mvp.md
*
* Endpoints:
* - POST /api/v1/platforms - Create platform
* - GET /api/v1/platforms - List platforms
*/
import { Router } from 'express';
import { daoFactoryService } from '../../services/dao-factory/dao-factory.service';
import { registryService } from '../../services/registry/registry.service';
import type { CreatePlatformInput } from '../../domain/dao/types';
export const platformsRoutes = Router();
// POST /api/v1/platforms - Create platform
platformsRoutes.post('/', async (req, res) => {
try {
const userId = (req as any).userId;
const input: CreatePlatformInput = req.body;
const result = await daoFactoryService.createPlatform(userId, input);
res.status(201).json(result);
} catch (error: any) {
res.status(400).json({
error: error.message || 'BAD_REQUEST',
message: error.message,
});
}
});
// GET /api/v1/platforms - List platforms
platformsRoutes.get('/', async (req, res) => {
try {
const platforms = await registryService.listPlatforms();
res.json({ items: platforms });
} catch (error: any) {
res.status(500).json({
error: 'INTERNAL_ERROR',
message: error.message,
});
}
});

View File

@@ -0,0 +1,52 @@
/**
* Vendor Routes
* Based on: api-mvp.md
*
* Endpoints:
* - POST /api/v1/platforms/{platform_id}/vendors - Register vendor
*/
import { Router } from 'express';
import { pdpService } from '../../services/pdp/pdp.service';
export const vendorRoutes = Router();
// POST /api/v1/platforms/{platform_id}/vendors - Register vendor
vendorRoutes.post('/:platformId/vendors', async (req, res) => {
try {
const userId = (req as any).userId;
const { platformId } = req.params;
const { display_name, contact } = req.body;
// Check PDP policy
const pdpResult = await pdpService.check(
'policy.vendor.register',
{ platformId },
{ userId, daoId: platformId, daoLevel: 'A2' }
);
if (pdpResult.decision !== 'allow') {
res.status(403).json({
error: 'ACCESS_DENIED',
message: pdpResult.reason || 'PDP denied',
});
return;
}
// TODO: Save vendor to database
const vendorId = `vendor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
res.status(201).json({
vendor_id: vendorId,
platform_id: platformId,
status: 'approved',
});
} catch (error: any) {
res.status(500).json({
error: 'INTERNAL_ERROR',
message: error.message,
});
}
});

View File

@@ -0,0 +1,72 @@
/**
* Wallet Routes
* Based on: api-mvp.md
*
* Endpoints:
* - GET /api/v1/wallet/me - Get user balances
* - POST /api/v1/wallet/check-access - Check access for action
*/
import { Router } from 'express';
import { walletService } from '../../services/wallet/wallet.service';
import type { AccessCheck } from '../../domain/wallet/types';
export const walletRoutes = Router();
// GET /api/v1/wallet/me - Get user balances
walletRoutes.get('/me', async (req, res) => {
try {
const userId = (req as any).userId;
const balances = await walletService.getBalances(userId);
res.json({
user_id: userId,
balances,
});
} catch (error: any) {
res.status(500).json({
error: 'INTERNAL_ERROR',
message: error.message,
});
}
});
// POST /api/v1/wallet/check-access - Check access
walletRoutes.post('/check-access', async (req, res) => {
try {
const userId = (req as any).userId;
const { check }: AccessCheck = req.body;
let allowed = false;
let reason: string | undefined;
switch (check) {
case 'dao.create':
allowed = await walletService.hasEnoughForDaoCreate(userId);
if (!allowed) reason = 'INSUFFICIENT_BALANCE';
break;
case 'vendor.register':
allowed = await walletService.hasEnoughForVendorRegister(userId);
if (!allowed) reason = 'INSUFFICIENT_STAKED_DAARION';
break;
case 'platform.create':
allowed = await walletService.hasEnoughForPlatformCreate(userId);
if (!allowed) reason = 'INSUFFICIENT_STAKED_DAARION';
break;
default:
reason = 'UNKNOWN_CHECK';
}
res.json({
allowed,
reason: allowed ? null : reason,
});
} catch (error: any) {
res.status(500).json({
error: 'INTERNAL_ERROR',
message: error.message,
});
}
});

View File

@@ -0,0 +1,39 @@
/**
* Auth Middleware
* Validates Bearer token
* MVP: Simple validation, replace with proper JWT validation later
*/
import type { Request, Response, NextFunction } from 'express';
export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.status(401).json({
error: 'UNAUTHORIZED',
message: 'Missing or invalid Authorization header',
});
return;
}
const token = authHeader.substring(7);
// MVP: Simple validation (just check token exists)
// TODO: Validate JWT token properly
if (!token) {
res.status(401).json({
error: 'UNAUTHORIZED',
message: 'Invalid token',
});
return;
}
// Attach user ID to request (MVP: extract from token)
// TODO: Decode JWT and extract userId
(req as any).userId = 'user_stub'; // Replace with actual user ID from token
next();
}

View File

@@ -0,0 +1,18 @@
/**
* Context Middleware
* Extracts X-DAO-ID header and attaches to request context
*/
import type { Request, Response, NextFunction } from 'express';
export function contextMiddleware(req: Request, res: Response, next: NextFunction): void {
const daoId = req.headers['x-dao-id'] as string | undefined;
if (daoId) {
(req as any).daoId = daoId;
}
next();
}

45
src/app.ts Normal file
View File

@@ -0,0 +1,45 @@
/**
* Application Entry Point
* Sets up HTTP server and registers routes
*/
import express from 'express';
import { config } from './infra/config/env';
import { logger } from './infra/logger/logger';
import { authMiddleware } from './api/middleware/auth.middleware';
import { contextMiddleware } from './api/middleware/context.middleware';
import { daoRoutes } from './api/http/dao.routes';
import { walletRoutes } from './api/http/wallet.routes';
import { pdpRoutes } from './api/http/pdp.routes';
import { vendorRoutes } from './api/http/vendor.routes';
import { platformsRoutes } from './api/http/platforms.routes';
import { agentsRoutes } from './api/http/agents.routes';
const app = express();
// Middleware
app.use(express.json());
app.use(authMiddleware);
app.use(contextMiddleware);
// Routes
app.use('/api/v1/dao', daoRoutes);
app.use('/api/v1/wallet', walletRoutes);
app.use('/api/v1/pdp', pdpRoutes);
app.use('/api/v1/platforms', platformsRoutes);
app.use('/api/v1/platforms', vendorRoutes); // Vendor routes under platforms
app.use('/api/v1', agentsRoutes);
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// Start server
const port = config.port;
app.listen(port, () => {
logger.info(`Server started on port ${port}`);
});
export default app;

View File

@@ -0,0 +1,30 @@
/**
* Pure domain logic for DAO operations
* No I/O, no side effects
*/
import type { DaoRecord, DaoLevel, FederationMode } from './types';
/**
* Check if DAO can become a SuperDAO
*/
export function canBecomeSuperDao(dao: DaoRecord, childCount: number): boolean {
return childCount >= 1 && dao.federationMode === 'none';
}
/**
* Check if DAO can join a federation
*/
export function canJoinFederation(dao: DaoRecord, targetLevel: DaoLevel): boolean {
// A3/A4 can join, exceptions for A2 handled by PDP
return (dao.level === 'A3' || dao.level === 'A4') && dao.federationMode === 'none';
}
/**
* Check if DAO can leave federation
*/
export function canLeaveFederation(dao: DaoRecord): boolean {
return dao.federationMode === 'member' && dao.parentDaoId !== null;
}

37
src/domain/dao/types.ts Normal file
View File

@@ -0,0 +1,37 @@
/**
* Domain types for DAO entities
* Based on: microdao-architecture.md, superdao-federation.md
*/
export type DaoLevel = 'A1' | 'A2' | 'A3' | 'A4';
export type DaoType = 'platform' | 'public' | 'private';
export type FederationMode = 'none' | 'member' | 'superdao';
export interface DaoRecord {
daoId: string;
name: string;
description?: string;
level: DaoLevel;
type: DaoType;
parentDaoId?: string | null;
federationMode: FederationMode;
createdAt: string;
updatedAt?: string;
}
export interface CreateDaoInput {
name: string;
description?: string;
type: 'public' | 'private';
level: 'A3' | 'A4';
settings?: Record<string, unknown>;
}
export interface CreatePlatformInput {
name: string;
slug: string;
description?: string;
domain?: string; // 'energy' | 'food' | 'water' | ...
}

View File

@@ -0,0 +1,38 @@
/**
* PDP Policy Model
* Based on: pdp_access.md, core-services-mvp.md
*/
export type Decision = 'allow' | 'deny' | 'require-elevation';
export type PolicyId =
| 'policy.dao.create'
| 'policy.vendor.register'
| 'policy.platform.create'
| 'policy.federation.join'
| 'policy.federation.leave'
| 'policy.federation.create-superdao'
| 'policy.federation.dissolve'
| 'policy.agent.run';
export interface PdpContext {
userId?: string;
daoId?: string;
daoLevel?: 'A1' | 'A2' | 'A3' | 'A4';
// Additional context: roles, balances, staking, etc.
[key: string]: unknown;
}
export interface PdpRequest {
policyId: PolicyId;
resource: Record<string, unknown>;
context: PdpContext;
}
export interface PdpResponse {
decision: Decision;
reason?: string;
details?: Record<string, unknown>;
}

14
src/domain/user/types.ts Normal file
View File

@@ -0,0 +1,14 @@
/**
* Domain types for User
*/
export type UserRole = 'owner' | 'admin' | 'member' | 'guest' | 'agent';
export interface User {
userId: string;
email?: string;
name?: string;
roles?: UserRole[];
}

View File

@@ -0,0 +1,27 @@
/**
* Domain types for Wallet
* Based on: core-services-mvp.md, tokenomics/city-tokenomics.md
*/
export type TokenSymbol = 'DAAR' | 'DAARION';
export interface Balance {
symbol: TokenSymbol;
amount: string; // Decimal as string to avoid precision issues
}
export interface WalletBalances {
userId: string;
balances: Balance[];
}
export interface AccessCheck {
check: 'dao.create' | 'vendor.register' | 'platform.create';
}
export interface AccessCheckResult {
allowed: boolean;
reason?: string;
}

24
src/index.css Normal file
View File

@@ -0,0 +1,24 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--primary: #3F51F5;
--success: #43A047;
--error: #E53935;
--gray-100: #F8F9FA;
--gray-200: #ECEFF1;
--gray-800: #263238;
}
body {
margin: 0;
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#root {
min-height: 100vh;
}

21
src/infra/config/env.ts Normal file
View File

@@ -0,0 +1,21 @@
/**
* Environment Configuration
* Load and validate environment variables
*/
export const config = {
port: parseInt(process.env.PORT || '3000', 10),
nodeEnv: process.env.NODE_ENV || 'development',
apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:3000',
// Database
dbUrl: process.env.DATABASE_URL || 'postgresql://localhost:5432/microdao',
// Auth
jwtSecret: process.env.JWT_SECRET || 'change-me-in-production',
// Wallet/Chain (future)
chainRpcUrl: process.env.CHAIN_RPC_URL || '',
};

20
src/infra/db/client.ts Normal file
View File

@@ -0,0 +1,20 @@
/**
* Database Client
* MVP: Placeholder for future DB connection
* TODO: Replace with actual DB client (PostgreSQL, etc.)
*/
// MVP: No-op
export const dbClient = {
connect: async () => {
// TODO: Implement actual DB connection
console.log('[DB] Connected (stub)');
},
disconnect: async () => {
// TODO: Implement actual DB disconnection
console.log('[DB] Disconnected (stub)');
},
};

View File

@@ -0,0 +1,44 @@
/**
* DAO Repository
* Database access layer for DAO records
* MVP: In-memory storage, replace with actual DB later
*/
import type { DaoRecord } from '../../domain/dao/types';
// MVP: In-memory storage
const daoStore: Map<string, DaoRecord> = new Map();
export const daoRepository = {
async save(record: DaoRecord): Promise<void> {
daoStore.set(record.daoId, record);
},
async findById(daoId: string): Promise<DaoRecord | null> {
return daoStore.get(daoId) || null;
},
async findAll(filter?: { level?: string; type?: string }): Promise<DaoRecord[]> {
const all = Array.from(daoStore.values());
if (!filter) {
return all;
}
return all.filter(dao => {
if (filter.level && dao.level !== filter.level) {
return false;
}
if (filter.type && dao.type !== filter.type) {
return false;
}
return true;
});
},
async delete(daoId: string): Promise<void> {
daoStore.delete(daoId);
},
};

View File

@@ -0,0 +1,27 @@
/**
* Logger
* MVP: Simple console logger
* Future: Replace with proper logging library (Winston, Pino, etc.)
*/
type LogLevel = 'info' | 'warn' | 'error' | 'debug';
export const logger = {
info: (message: string, ...args: unknown[]) => {
console.log(`[INFO] ${message}`, ...args);
},
warn: (message: string, ...args: unknown[]) => {
console.warn(`[WARN] ${message}`, ...args);
},
error: (message: string, ...args: unknown[]) => {
console.error(`[ERROR] ${message}`, ...args);
},
debug: (message: string, ...args: unknown[]) => {
console.debug(`[DEBUG] ${message}`, ...args);
},
};

14
src/main.tsx Normal file
View File

@@ -0,0 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
);

View File

@@ -0,0 +1,109 @@
/**
* DAOFactory Service (MVP)
* Based on: core-services-mvp.md
*
* Responsibilities:
* - Create new DAO (A3/A4)
* - Create platforms (A2)
* - Validate input
* - Call PDP for access checks
* - Write DAO to Registry
*/
import type { CreateDaoInput, CreatePlatformInput } from '../../domain/dao/types';
import { pdpService } from '../pdp/pdp.service';
import { walletService } from '../wallet/wallet.service';
import { registryService } from '../registry/registry.service';
export class DaoFactoryService {
/**
* Create a new MicroDAO (A3 or A4)
*/
async createDao(userId: string, input: CreateDaoInput): Promise<{ daoId: string }> {
// 1. Check wallet balance
const hasEnough = await walletService.hasEnoughForDaoCreate(userId);
if (!hasEnough) {
throw new Error('INSUFFICIENT_BALANCE: Need 1 DAAR or 0.01 DAARION');
}
// 2. Check PDP policy
const pdpResult = await pdpService.check(
'policy.dao.create',
{ type: 'dao' },
{ userId, daoLevel: input.level }
);
if (pdpResult.decision !== 'allow') {
throw new Error(`ACCESS_DENIED: ${pdpResult.reason || 'PDP denied'}`);
}
// 3. Create DAO record
const daoId = this.generateDaoId();
const daoRecord = {
daoId,
name: input.name,
description: input.description,
level: input.level,
type: input.type,
parentDaoId: null,
federationMode: 'none' as const,
createdAt: new Date().toISOString(),
};
// 4. Save to Registry
await registryService.saveDao(daoRecord);
return { daoId };
}
/**
* Create a new platform (A2)
*/
async createPlatform(userId: string, input: CreatePlatformInput): Promise<{ daoId: string }> {
// 1. Check wallet balance
const hasEnough = await walletService.hasEnoughForPlatformCreate(userId);
if (!hasEnough) {
throw new Error('INSUFFICIENT_BALANCE: Need 1 DAARION staked');
}
// 2. Check PDP policy
const pdpResult = await pdpService.check(
'policy.platform.create',
{ type: 'platform' },
{ userId, daoLevel: 'A2' }
);
if (pdpResult.decision !== 'allow') {
throw new Error(`ACCESS_DENIED: ${pdpResult.reason || 'PDP denied'}`);
}
// 3. Create platform record
const daoId = this.generateDaoId();
const daoRecord = {
daoId,
name: input.name,
description: input.description,
level: 'A2' as const,
type: 'platform' as const,
parentDaoId: null,
federationMode: 'none' as const,
createdAt: new Date().toISOString(),
};
// 4. Save to Registry
await registryService.saveDao(daoRecord);
return { daoId };
}
private generateDaoId(): string {
// MVP: Simple UUID-like generation
// TODO: Use proper UUID library
return `dao_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
// Singleton instance
export const daoFactoryService = new DaoFactoryService();

View File

@@ -0,0 +1,100 @@
/**
* PDP Service (MVP)
* Based on: pdp_access.md, core-services-mvp.md
*
* Responsibilities:
* - Centralized access decision making
* - Interpret policies from pdp_access.md
* - Provide simple API for other services
*/
import type { PdpRequest, PdpResponse, PolicyId, PdpContext } from '../../domain/pdp/policy.model';
import { policiesConfig } from './policies.config';
import { walletService } from '../wallet/wallet.service';
export class PdpService {
/**
* Check policy and return decision
*/
async check(
policyId: PolicyId,
resource: Record<string, unknown>,
context: PdpContext
): Promise<PdpResponse> {
const policy = policiesConfig[policyId];
if (!policy) {
return {
decision: 'deny',
reason: `Policy ${policyId} not found`,
};
}
// Evaluate policy conditions
const result = await this.evaluatePolicy(policy, resource, context);
return result;
}
private async evaluatePolicy(
policy: any,
resource: Record<string, unknown>,
context: PdpContext
): Promise<PdpResponse> {
// MVP: Simple evaluation
// Future: More complex condition evaluation
// Example: policy.dao.create
if (policy.id === 'policy.dao.create') {
const hasEnough = await walletService.hasEnoughForDaoCreate(context.userId || '');
if (!hasEnough) {
return {
decision: 'deny',
reason: 'INSUFFICIENT_BALANCE',
details: {
required: { DAAR: 1.0, DAARION: 0.01 },
},
};
}
return { decision: 'allow' };
}
// Example: policy.platform.create
if (policy.id === 'policy.platform.create') {
const hasEnough = await walletService.hasEnoughForPlatformCreate(context.userId || '');
if (!hasEnough) {
return {
decision: 'deny',
reason: 'INSUFFICIENT_BALANCE',
details: {
required: { DAARION: 1.0 },
},
};
}
return { decision: 'allow' };
}
// Example: policy.vendor.register
if (policy.id === 'policy.vendor.register') {
const hasEnough = await walletService.hasEnoughForVendorRegister(context.userId || '');
if (!hasEnough) {
return {
decision: 'deny',
reason: 'INSUFFICIENT_STAKED_DAARION',
details: {
required: { DAARION: 0.01 },
},
};
}
return { decision: 'allow' };
}
// Default: allow (for MVP, can be more restrictive later)
return { decision: 'allow' };
}
}
// Singleton instance
export const pdpService = new PdpService();

View File

@@ -0,0 +1,76 @@
/**
* Policies Configuration
* Based on: pdp_access.md
*
* Initial set of policies for MVP
*/
export const policiesConfig = {
'policy.dao.create': {
id: 'policy.dao.create',
description: 'Створення нового MicroDAO',
conditions: [
{
type: 'or',
rules: [
{ type: 'balance', token: 'DAAR', gte: 1 },
{ type: 'balance', token: 'DAARION', gte: 0.01 },
],
},
],
},
'policy.vendor.register': {
id: 'policy.vendor.register',
description: 'Реєстрація вендора на платформі',
conditions: [
{ type: 'staked', token: 'DAARION', gte: 0.01 },
],
},
'policy.platform.create': {
id: 'policy.platform.create',
description: 'Створення платформи',
conditions: [
{ type: 'staked', token: 'DAARION', gte: 1 },
],
},
'policy.federation.join': {
id: 'policy.federation.join',
description: 'Вступ DAO до SuperDAO',
conditions: [
{ type: 'role', value: 'owner' },
{ type: 'target', property: 'federation_mode', value: 'superdao' },
],
},
'policy.federation.leave': {
id: 'policy.federation.leave',
description: 'Вихід DAO з SuperDAO',
conditions: [
{ type: 'role', value: 'owner' },
],
},
'policy.federation.create-superdao': {
id: 'policy.federation.create-superdao',
description: 'Створення SuperDAO',
conditions: [
{ type: 'role', value: 'owner' },
{ type: 'dao', property: 'child_count', gte: 1 },
],
},
'policy.federation.dissolve': {
id: 'policy.federation.dissolve',
description: 'Розформування федерації',
conditions: [
{ type: 'role', value: 'owner' },
{ type: 'dao', property: 'level', ne: 'A1' },
],
},
'policy.agent.run': {
id: 'policy.agent.run',
description: 'Запуск агента',
conditions: [
{ type: 'agent', property: 'registered', value: true },
],
},
} as const;

View File

@@ -0,0 +1,47 @@
/**
* Registry Service (MVP)
* Based on: core-services-mvp.md
*
* Responsibilities:
* - Store all DAO information
* - Mark DAO as platform (A2) or MicroDAO (A3/A4)
* - Provide public catalog of DAO/platforms
*/
import type { DaoRecord } from '../../domain/dao/types';
import { daoRepository } from '../../infra/db/dao.repository';
export class RegistryService {
/**
* Save DAO record to registry
*/
async saveDao(record: DaoRecord): Promise<void> {
await daoRepository.save(record);
}
/**
* Get DAO by ID
*/
async getDaoById(daoId: string): Promise<DaoRecord | null> {
return daoRepository.findById(daoId);
}
/**
* List DAOs with optional filters
*/
async listDaos(filter?: { level?: string; type?: string }): Promise<DaoRecord[]> {
return daoRepository.findAll(filter);
}
/**
* List all platforms (A2, type=platform)
*/
async listPlatforms(): Promise<DaoRecord[]> {
return daoRepository.findAll({ level: 'A2', type: 'platform' });
}
}
// Singleton instance
export const registryService = new RegistryService();

View File

@@ -0,0 +1,36 @@
/**
* Wallet Adapter (MVP Stub)
*
* On MVP: returns mock data or reads from DB/stub
* Future: integrate with on-chain data
*/
import type { Balance } from '../../domain/wallet/types';
/**
* Get balances from external source (on-chain / DB / stub)
*/
export async function getBalances(userId: string): Promise<Balance[]> {
// MVP: Return mock data
// TODO: Replace with actual DB/on-chain integration
return [
{ symbol: 'DAAR', amount: '0.0' },
{ symbol: 'DAARION', amount: '0.0' },
];
}
/**
* Get staked DAARION amount
*/
export async function getStakedDaarion(userId: string): Promise<number> {
// MVP: Return mock data
// TODO: Replace with actual DB/on-chain integration
return 0.0;
}
export const walletAdapter = {
getBalances,
getStakedDaarion,
};

View File

@@ -0,0 +1,15 @@
/**
* Wallet Service Interface
* Based on: core-services-mvp.md
*/
import type { Balance } from '../../domain/wallet/types';
export interface WalletService {
getBalances(userId: string): Promise<Balance[]>;
hasEnoughForDaoCreate(userId: string): Promise<boolean>;
hasEnoughForVendorRegister(userId: string): Promise<boolean>;
hasEnoughForPlatformCreate(userId: string): Promise<boolean>;
}

View File

@@ -0,0 +1,61 @@
/**
* Wallet Service (MVP)
* Based on: core-services-mvp.md
*
* Responsibilities:
* - Read DAAR / DAARION balances
* - Provide helper functions for access checks
*/
import type { WalletService as IWalletService } from './wallet.interface';
import { walletAdapter } from './wallet.adapter';
import type { Balance, TokenSymbol } from '../../domain/wallet/types';
export class WalletService implements IWalletService {
/**
* Get user balances for DAAR and DAARION
*/
async getBalances(userId: string): Promise<Balance[]> {
return walletAdapter.getBalances(userId);
}
/**
* Check if user has enough tokens to create a DAO
* Requires: 1 DAAR OR 0.01 DAARION
*/
async hasEnoughForDaoCreate(userId: string): Promise<boolean> {
const balances = await this.getBalances(userId);
const daar = balances.find(b => b.symbol === 'DAAR');
const daarion = balances.find(b => b.symbol === 'DAARION');
// Check: 1 DAAR OR 0.01 DAARION
const hasEnoughDaar = daar && parseFloat(daar.amount) >= 1.0;
const hasEnoughDaarion = daarion && parseFloat(daarion.amount) >= 0.01;
return hasEnoughDaar || hasEnoughDaarion;
}
/**
* Check if user has enough staked DAARION for vendor registration
* Requires: 0.01 DAARION staked
*/
async hasEnoughForVendorRegister(userId: string): Promise<boolean> {
const staked = await walletAdapter.getStakedDaarion(userId);
return staked >= 0.01;
}
/**
* Check if user has enough staked DAARION for platform creation
* Requires: 1 DAARION staked
*/
async hasEnoughForPlatformCreate(userId: string): Promise<boolean> {
const staked = await walletAdapter.getStakedDaarion(userId);
return staked >= 1.0;
}
}
// Singleton instance
export const walletService = new WalletService();