UNPKG

oneie

Version:

Build apps, websites, and AI agents in English. Zero-interaction setup for AI agents (Claude Code, Cursor, Windsurf). Download to your computer, run in the cloud, deploy to the edge. Open source and free forever.

924 lines (718 loc) 23.6 kB
--- title: Workflow dimension: connections category: workflow.md tags: agent, ai, backend, connections, events, frontend, knowledge, ontology, people, things related_dimensions: events, groups, knowledge, people, things scope: global created: 2025-11-03 updated: 2025-11-03 version: 1.0.0 ai_context: | This document is part of the connections dimension in the workflow.md category. Location: one/connections/workflow.md Purpose: Documents workflow - ontology-driven development Related dimensions: events, groups, knowledge, people, things For AI agents: Read this to understand workflow. --- # Workflow - Ontology-Driven Development **Version:** 1.0.0 **Purpose:** Define the exact workflow for building features on the ONE Platform using the 6-dimension ontology --- ## Overview Every feature in ONE Platform follows a 6-phase workflow that ensures consistency, quality, and adherence to the ontology. This workflow prevents technical debt and makes AI agents more effective at code generation. **The Golden Rule:** If you can't map your feature to the 6 dimensions (organizations, people, things, connections, events, knowledge), you're thinking about it wrong. --- ## The 6-Phase Workflow ``` Phase 1: UNDERSTAND Phase 2: MAP TO ONTOLOGY Phase 3: DESIGN SERVICES Phase 4: IMPLEMENT BACKEND Phase 5: BUILD FRONTEND Phase 6: TEST & DOCUMENT ``` --- ## Phase 1: UNDERSTAND **Goal:** Fully understand the feature requirements and context. ### Step 1.1: Read Context Documentation **MANDATORY READING ORDER:** 1. **`one/knowledge/ontology.md`** (5 min) - The 6-dimension universe 2. **`one/things/README.md`** (2 min) - Understand thing types 3. **`one/connections/README.md`** (2 min) - Understand connection types 4. **`one/events/README.md`** (2 min) - Understand event types 5. **`one/things/files.md`** (3 min) - File organization **For specific features, also read:** - **Blockchain features:** `one/things/Sui.md`, `one/connections/cryptonetworks.md` - **Cycle features:** `one/events/CycleRevenue.md` - **Protocol features:** `one/connections/protocols.md` + specific protocol doc - **Integration features:** `one/connections/communications.md` + specific integration doc - **Agent features:** `one/things/AgentKit.md`, `one/things/CopilotKit.md` ### Step 1.2: Identify Feature Category **Ask yourself:** - Is this a new entity type? - Is this a new relationship? - Is this a new action/event? - Is this a query/reporting feature? - Does it involve external protocols (A2A, ACP, AP2, X402)? - Does it involve blockchain (SUI, Solana, Base)? ### Step 1.3: Find Similar Patterns **Search for existing implementations:** - Similar entity types in `convex/schema.ts` - Similar mutations in `convex/mutations/` - Similar queries in `convex/queries/` - Similar services in `convex/services/` - Similar components in `src/components/features/` **Example:** If building "NFT Minting", search for: - Token minting patterns - Blockchain transaction patterns - Payment processing patterns --- ## Phase 2: MAP TO ONTOLOGY **Goal:** Map the feature to the 6-dimension ontology BEFORE writing any code. ### Step 2.1: Identify Things **Question:** What entities are involved? **Template:** ``` Things: - <thing_name> (type: "<thing_type>") properties: { <key properties> } - <thing_name> (type: "<thing_type>") properties: { <key properties> } ``` **Example: Token Purchase** ``` Things: - user (type: "creator" or "audience_member") properties: { email, walletAddress } - token (type: "token") properties: { contractAddress, totalSupply, price } ``` ### Step 2.2: Identify Connections **Question:** How do entities relate to each other? **Template:** ``` Connections: - <from_thing> <to_thing> (relationshipType: "<type>") metadata: { <relationship details> } ``` **Example: Token Purchase** ``` Connections: - user token (relationshipType: "holds_tokens") metadata: { balance: 100, balanceObjectId: "0x123...", // SUI coin object walletAddress: "0xabc...", acquiredAt: Date.now() } ``` ### Step 2.3: Identify Events **Question:** What actions need to be logged? **Template:** ``` Events: - <event_type> actorId: <who_did_it> targetId: <what_it_affected> metadata: { <event details> } ``` **Example: Token Purchase** ``` Events: - tokens_purchased actorId: userId targetId: tokenId metadata: { network: "sui", amount: 100, paidAmount: 0.5, // SUI paid txDigest: "9mKG...", } ``` ### Step 2.4: Tags & Knowledge **Question:** What categorization or semantic data is needed? Use tags for categorization; use knowledge items for RAG when needed. **Template:** ``` Tags: - thingTags: [<tag1>, <tag2>] // applied via junction Knowledge (optional): - knowledge_item things for chunks/summaries - embedding things for vectors when stored explicitly ``` **Example: Token Purchase** ``` Tags: - ["network:sui", "protocol:sui-move", "status:active"] Knowledge: - None (tokens typically don’t require RAG) ``` ### Step 2.5: Validate Ontology Mapping **Checklist:** - [ ] All entities map to existing thing types (or propose new type) - [ ] All relationships use existing connection types - [ ] All events use existing event types - [ ] Metadata captures protocol/network specifics - [ ] No custom tables needed (everything fits in 6 dimensions) **If validation fails:** Re-think the feature. The ontology is intentionally complete. ### Step 2.6: Protocol Mapping Declare protocol usage explicitly across primitives. - Thing: set `properties.protocol` (e.g., `openai`, `ap2`, `acp`, `x402`, `sui`). - Connection: set `metadata.protocol` for protocol-scoped relationships. - Event: set `metadata.protocol` and echo key protocol fields (ids, tx hashes, network, status). --- ## Phase 3: DESIGN SERVICES **Goal:** Design pure Effect.ts services for business logic. ### Step 3.1: Define Service Interface **Pattern:** ```typescript // convex/services/<category>/<service>.ts export class <Service>Service extends Effect.Service<<Service>Service>()( "<Service>Service", { effect: Effect.gen(function* () { const db = yield* ConvexDatabase; const <provider> = yield* <Provider>; return { <method>: (args: <Args>) => Effect.gen(function* () { // Business logic here }), }; }), dependencies: [ConvexDatabase.Default, <Provider>.Default], } ) {} ``` **Example: Token Purchase Service** ```typescript // convex/services/blockchain/token.ts export class TokenService extends Effect.Service<TokenService>()( "TokenService", { effect: Effect.gen(function* () { const db = yield* ConvexDatabase; const sui = yield* SuiProvider; return { purchase: (args: PurchaseTokenArgs) => Effect.gen(function* () { // 1. Validate const user = yield* Effect.tryPromise(() => db.get(args.userId)); const token = yield* Effect.tryPromise(() => db.get(args.tokenId)); if (!user) { return yield* Effect.fail(new UserNotFoundError()); } // 2. Execute blockchain transaction const tx = new Transaction(); // ... build transaction const result = yield* sui.executeTransaction(tx); // 3. Create/update connection yield* Effect.tryPromise(() => db.insert("connections", { fromThingId: args.userId, toThingId: args.tokenId, relationshipType: "holds_tokens", metadata: { network: "sui", balance: args.amount, balanceObjectId: result.objectId, walletAddress: user.properties.walletAddress, acquiredAt: Date.now(), }, createdAt: Date.now(), }) ); // 4. Log event yield* Effect.tryPromise(() => db.insert("events", { type: "tokens_purchased", actorId: args.userId, targetId: args.tokenId, timestamp: Date.now(), metadata: { network: "sui", amount: args.amount, txDigest: result.digest, }, }) ); return { success: true, txDigest: result.digest }; }), }; }), dependencies: [ConvexDatabase.Default, SuiProvider.Default], } ) {} ``` ### Step 3.2: Define Error Types **Pattern:** ```typescript // convex/services/<category>/errors.ts import { Data } from "effect"; export class <Error>Error extends Data.TaggedError("<Error>Error")<{ message: string; code?: string; }> {} ``` **Example:** ```typescript // convex/services/blockchain/errors.ts export class UserNotFoundError extends Data.TaggedError("UserNotFoundError")<{ message: string; userId: string; }> {} export class InsufficientBalanceError extends Data.TaggedError( "InsufficientBalanceError" )<{ message: string; required: number; available: number; }> {} export class BlockchainError extends Data.TaggedError("BlockchainError")<{ message: string; network: "sui" | "solana" | "base"; txHash?: string; }> {} ``` ### Step 3.3: Service Rules **MUST:** - Pure functions only (no side effects outside Effect) - Explicit types (no `any`) - Typed errors with `_tag` pattern - Dependency injection via Effect.Service - Business logic ONLY (no Convex-specific code) **MUST NOT:** - Use `console.log` (use Effect.log) - Catch errors with try/catch (use Effect.tryPromise) - Mix UI logic with business logic - Hard-code configuration (use environment variables) ### Step 3.4: Service Invariants Checklist - Validate inputs with `v` validators and explicit types. - Enforce tenancy and roles via connections before mutating. - Prefer consolidated types (`transacted`, `communicated`, `delegated`). - Always include `metadata.protocol` when a provider/protocol is involved. - Ensure indexes exist for every non-id lookup used. --- ## Phase 4: IMPLEMENT BACKEND **Goal:** Create thin Convex wrappers that call Effect.ts services. ### Step 4.1: Create Mutation **Pattern:** ```typescript // convex/mutations/<domain>.ts import { confect } from "@/convex/lib/confect"; import { v } from "convex/values"; import { Effect } from "effect"; import { <Service>Service } from "@/convex/services/<category>/<service>"; import { MainLayer } from "@/convex/layers"; export const <methodName> = confect.mutation({ args: { // Convex validators <arg>: v.id("things"), <arg>: v.number(), }, handler: (ctx, args) => Effect.gen(function* () { const service = yield* <Service>Service; return yield* service.<method>(args); }).pipe(Effect.provide(MainLayer)), }); ``` **Example: Token Purchase Mutation** ```typescript // convex/mutations/tokens.ts export const purchase = confect.mutation({ args: { tokenId: v.id("things"), amount: v.number(), }, handler: (ctx, args) => Effect.gen(function* () { const tokenService = yield* TokenService; const userId = yield* Effect.fromNullable( ctx.auth.userId, () => new UnauthorizedError({ message: "Must be logged in" }) ); return yield* tokenService.purchase({ userId, tokenId: args.tokenId, amount: args.amount, }); }).pipe(Effect.provide(MainLayer)), }); ``` ### Step 4.2: Create Query **Pattern:** ```typescript // convex/queries/<domain>.ts export const <queryName> = confect.query({ args: { <arg>: v.id("things"), }, handler: (ctx, args) => Effect.gen(function* () { const service = yield* <Service>Service; return yield* service.<method>(args); }).pipe(Effect.provide(MainLayer)), }); ``` **Example: Get Token Balance** ```typescript // convex/queries/tokens.ts export const getBalance = confect.query({ args: { userId: v.id("things"), tokenId: v.id("things"), }, handler: (ctx, args) => Effect.gen(function* () { const tokenService = yield* TokenService; return yield* tokenService.getBalance(args); }).pipe(Effect.provide(MainLayer)), }); ``` ### Step 4.3: Backend Rules **Convex functions should:** - Validate arguments with Convex validators - Call service methods (thin wrapper) - Provide MainLayer - Handle authentication/authorization - Return serializable data only **Convex functions should NOT:** - Contain business logic (use services) - Make external API calls directly (use services) - Parse/transform complex data (use services) ### Step 4.4: Streaming & AG-UI (Optional) For AI outputs that the UI should render dynamically, emit `communication_event` rows with structured payloads and protocol metadata. ```typescript await ctx.db.insert("events", { type: "communication_event", actorId: agentId, targetId: conversationId, timestamp: Date.now(), metadata: { protocol: "openai", messageType: "ui", component: "Card", data: { title: "Campaign Outline", items: ["Hook", "CTA", "Channels"] }, }, }); ``` See `one/things/CopilotKit.md` and `one/things/AgentKit.md` for details. --- ## Phase 5: BUILD FRONTEND **Goal:** Create React components that use Convex hooks. ### Step 5.1: Create Feature Component **Pattern:** ```typescript // src/components/features/<domain>/<Component>.tsx import { useMutation, useQuery } from "convex/react"; import { api } from "@/convex/_generated/api"; import { Button } from "@/components/ui/button"; import type { Id } from "@/convex/_generated/dataModel"; export function <Component>({ <prop> }: { <prop>: <Type> }) { const <mutation> = useMutation(api.<domain>.<method>); const <query> = useQuery(api.<domain>.<query>, { <arg> }); return ( <div> {/* UI here */} <Button onClick={() => <mutation>({ <arg> })}> Action </Button> </div> ); } ``` **Example: Token Purchase Component** ```typescript // src/components/features/tokens/TokenPurchase.tsx import { useMutation, useQuery } from "convex/react"; import { api } from "@/convex/_generated/api"; import { Button } from "@/components/ui/button"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import type { Id } from "@/convex/_generated/dataModel"; import { useState } from "react"; export function TokenPurchase({ tokenId }: { tokenId: Id<"things"> }) { const [amount, setAmount] = useState(100); const [loading, setLoading] = useState(false); const purchase = useMutation(api.tokens.purchase); const balance = useQuery(api.tokens.getBalance, { tokenId }); const token = useQuery(api.tokens.get, { id: tokenId }); const handlePurchase = async () => { setLoading(true); try { await purchase({ tokenId, amount }); } catch (error) { console.error("Purchase failed:", error); } finally { setLoading(false); } }; return ( <Card> <CardHeader> <CardTitle>{token?.name || "Token"}</CardTitle> </CardHeader> <CardContent> <p>Your balance: {balance?.metadata.balance || 0}</p> <p>Price: {token?.properties.price} SUI</p> <div className="flex gap-2 mt-4"> <input type="number" value={amount} onChange={(e) => setAmount(Number(e.target.value))} className="border rounded px-2" /> <Button onClick={handlePurchase} disabled={loading}> {loading ? "Purchasing..." : `Buy ${amount} Tokens`} </Button> </div> </CardContent> </Card> ); } ``` ### Step 5.2: Create Astro Page (if needed) **Pattern:** ```astro --- // src/pages/<domain>/[id].astro import { ConvexHttpClient } from "convex/browser"; import { api } from "@/convex/_generated/api"; import Layout from "@/layouts/Layout.astro"; import <Component> from "@/components/features/<domain>/<Component>"; const convex = new ConvexHttpClient(import.meta.env.PUBLIC_CONVEX_URL); const <data> = await convex.query(api.<domain>.<query>, { id: Astro.params.id, }); --- <Layout title={<data>.name}> <h1>{<data>.name}</h1> <<Component> client:load <prop>={<data>._id} /> </Layout> ``` **Example: Token Detail Page** ```astro --- // src/pages/tokens/[id].astro import { ConvexHttpClient } from "convex/browser"; import { api } from "@/convex/_generated/api"; import Layout from "@/layouts/Layout.astro"; import TokenPurchase from "@/components/features/tokens/TokenPurchase"; const convex = new ConvexHttpClient(import.meta.env.PUBLIC_CONVEX_URL); const token = await convex.query(api.tokens.get, { id: Astro.params.id as any, }); --- <Layout title={token.name}> <div class="container mx-auto p-4"> <h1 class="text-3xl font-bold mb-4">{token.name}</h1> <p class="text-muted-foreground mb-8">{token.properties.description}</p> <TokenPurchase client:load tokenId={token._id} /> </div> </Layout> ``` ### Step 5.3: Frontend Rules **Components should:** - Use Convex hooks (`useQuery`, `useMutation`) - Use shadcn/ui components - Handle loading states - Handle error states - Be small and focused (single responsibility) When rendering AG-UI messages, map `communication_event` payloads to PromptKit components (no raw HTML injection). **Components should NOT:** - Contain business logic (use mutations) - Make API calls directly (use Convex) - Mutate props - Use class components (use function components) --- ## Phase 6: TEST & DOCUMENT **Goal:** Ensure quality and maintainability. ### Step 6.1: Write Unit Tests **Pattern:** ```typescript // tests/unit/services/<service>.test.ts import { describe, it, expect } from "vitest"; import { Effect, Layer } from "effect"; import { <Service>Service } from "@/convex/services/<category>/<service>"; import { ConvexDatabase } from "@/convex/lib/convex-database"; describe("<Service>Service.<method>", () => { it("should <expected behavior>", async () => { // Mock dependencies const MockDatabase = Layer.succeed(ConvexDatabase, { get: () => Effect.succeed({ _id: "123", type: "token" }), insert: () => Effect.succeed("456"), // ... }); const TestLayer = MockDatabase; const result = await Effect.runPromise( Effect.gen(function* () { const service = yield* <Service>Service; return yield* service.<method>({ <args> }); }).pipe(Effect.provide(TestLayer)) ); expect(result.success).toBe(true); }); it("should fail when <error condition>", async () => { // Test error cases }); }); ``` **Example: Token Service Test** ```typescript // tests/unit/services/token.test.ts describe("TokenService.purchase", () => { it("should purchase tokens successfully", async () => { const MockSUI = Layer.succeed(SuiProvider, { executeTransaction: () => Effect.succeed({ digest: "9mKG...", objectId: "0x123...", }), }); const MockDB = Layer.succeed(ConvexDatabase, { get: (id) => Effect.succeed({ _id: id, type: "token", properties: { price: 0.1 }, }), insert: () => Effect.succeed("connection_id"), }); const TestLayer = Layer.merge(MockSUI, MockDB); const result = await Effect.runPromise( Effect.gen(function* () { const service = yield* TokenService; return yield* service.purchase({ userId: "user_123", tokenId: "token_456", amount: 100, }); }).pipe(Effect.provide(TestLayer)) ); expect(result.success).toBe(true); expect(result.txDigest).toBe("9mKG..."); }); }); ``` ### Step 6.2: Write Integration Tests **Pattern:** ```typescript // tests/integration/<feature>.test.ts import { describe, it, expect } from "vitest"; import { ConvexTestingHelper } from "convex-test"; describe("<Feature> Integration", () => { it("should complete full flow", async () => { const t = new ConvexTestingHelper(); // Test full flow: mutation service database event }); }); ``` ### Step 6.3: Update Documentation **Required updates:** - Add to `one/things/README.md` if new thing type - Add to `one/connections/README.md` if new connection type - Add to `one/events/README.md` if new event type - Update `one/things/files.md` with new files - Add code examples to relevant docs **Optional updates:** - Create dedicated doc in `one/things/<ThingType>.md` - Create dedicated doc in `one/events/<EventCategory>.md` - Update `one/connections/patterns.md` if novel pattern --- ## Workflow Checklist ### Before Starting - [ ] Read `one/knowledge/ontology.md` - [ ] Understand the 6-dimension model - [ ] Identify similar existing patterns - [ ] Map feature to ontology (Phase 2) ### During Implementation - [ ] Design Effect.ts services (Phase 3) - [ ] Create thin Convex wrappers (Phase 4) - [ ] Build React components (Phase 5) - [ ] Follow naming conventions - [ ] Use explicit types - [ ] Set `properties.protocol` / `metadata.protocol` as applicable - [ ] Ensure indexes exist for all queries ### After Implementation - [ ] Write unit tests (Phase 6) - [ ] Write integration tests (Phase 6) - [ ] Update documentation (Phase 6) - [ ] Run TypeScript checks (`bunx astro check`) - [ ] Run tests (`bun test`) - [ ] Review safety gates (tenancy, approvals, audit events) --- ## Anti-Patterns to Avoid ### ❌ DON'T: Skip Ontology Mapping ```typescript // BAD: Creating custom tables await db.insert("custom_nft_table", { ... }); ``` ### ✅ DO: Map to Ontology ```typescript // GOOD: Using things table await db.insert("things", { type: "nft", properties: { ... } }); ``` ### ❌ DON'T: Put Business Logic in Convex Functions ```typescript // BAD export const purchase = mutation({ handler: async (ctx, args) => { const balance = await getBalance(args.userId); if (balance < args.price) { throw new Error("Insufficient balance"); } // ... more logic }, }); ``` ### ✅ DO: Put Business Logic in Services ```typescript // GOOD export const purchase = confect.mutation({ handler: (ctx, args) => Effect.gen(function* () { const service = yield* TokenService; return yield* service.purchase(args); }).pipe(Effect.provide(MainLayer)), }); ``` ### ❌ DON'T: Use `any` Types ```typescript // BAD const result: any = await service.execute(); ``` ### ✅ DO: Use Explicit Types ```typescript // GOOD const result: PurchaseResult = await service.purchase(args); ``` --- ## Summary **The 6-Phase Workflow:** 1. **UNDERSTAND** - Read docs, identify category 2. **MAP TO ONTOLOGY** - Things, connections, events, tags 3. **DESIGN SERVICES** - Pure Effect.ts business logic 4. **IMPLEMENT BACKEND** - Thin Convex wrappers 5. **BUILD FRONTEND** - React components with Convex hooks 6. **TEST & DOCUMENT** - Unit tests, integration tests, docs **Key Principles:** - Ontology first, code second - Pure functions, explicit types - Business logic in services - Thin wrappers everywhere - Test everything Additional principles: - Explicit protocol mapping (`properties.protocol`, `metadata.protocol`). - Prefer consolidated connection/event types. - Use tags for categorization; use knowledge things for RAG. **This workflow ensures every feature makes the codebase BETTER, not worse.**