| title | Rules |
|---|---|
| dimension | knowledge |
| category | rules.md |
| tags | agent, ai, connections, events, ontology |
| related_dimensions | connections, events, people, things |
| scope | global |
| created | 2025-11-03 |
| updated | 2025-11-03 |
| version | 1.0.0 |
| ai_context | This document is part of the knowledge dimension in the rules.md category. Location: one/knowledge/rules.md Purpose: Documents one platform - ai development rules Related dimensions: connections, events, people, things For AI agents: Read this to understand rules. |
Version: 1.0.0
Last Updated: 2025-01-15
Purpose: Golden rules for AI agents building the ONE platform
The ONE platform gets BETTER with scale because:
- Every new feature adds to the pattern library
- Types prevent breaking changes automatically
- The ontology constrains design space productively
- Services compose rather than duplicate
- Tests validate that new code works with existing code
AI agents must read this file FIRST before any code generation.
┌─────────────┐
│ ENTITIES │ ← Everything is an entity (users, agents, content, tokens)
└──────┬──────┘
│
├──→ ┌──────────────┐
│ │ CONNECTIONS │ ← All relationships between entities
│ └──────────────┘
│
├──→ ┌──────────────┐
│ │ EVENTS │ ← All actions, changes, time-series data
│ └──────────────┘
│
└──→ ┌──────────────┐
│ TAGS │ ← Categorization and taxonomy
└──────────────┘
Every feature you build MUST use this ontology. No exceptions.
- What: All nouns in the system
- Examples: creator, ai_clone, blog_post, token, course, message
- Fields: type, name, properties (JSON), status, timestamps
- Rule: If you're modeling a "thing", it's an entity
- What: All relationships between entities
- Examples: creator owns token, user enrolled_in course, agent powers clone
- Fields: fromEntityId, toEntityId, relationshipType, metadata
- Rule: If you're modeling "X relates to Y", it's a connection
- What: All actions, state changes, metrics
- Examples: token_purchased, content_published, clone_interaction
- Fields: entityId, eventType, timestamp, metadata, actorId
- Rule: If you're modeling "X happened at time T", it's an event
- What: Categorization, taxonomy, filtering
- Examples: industry:fitness, skill:video-editing, format:tutorial
- Fields: name, category, color, icon
- Rule: If you're modeling "categories", use tags + entityTags junction
- Astro 5.14+ - Pages and routing
- React 19 - Interactive components
- shadcn/ui - UI components (50+ pre-installed)
- Tailwind CSS v4 - Styling (utility-first)
- TypeScript 5.9+ - Strict mode, no implicit any
- Convex - Real-time database + backend functions
- Effect.ts - Service layer (typed errors, DI, composition)
- Confect - Bridge between Effect.ts and Convex
- Better Auth - Authentication (email/password + OAuth)
- Resend - Transactional emails
- Stripe Connect - Payments
- Base L2 - Blockchain for tokens
- ElevenLabs - Voice cloning
- OpenAI - AI/LLM operations
Before generating ANY code, read these files in order:
.ai/context/ontology.md- Data model.ai/context/architecture.md- System design.ai/context/patterns.md- Code patterns.ai/specs/[relevant-feature].md- Feature spec.ai/context/file-map.md- Where files live
// ✅ CORRECT: Explicit types
export const createClone = (creatorId: Id<"entities">): Effect.Effect<
{ cloneId: Id<"entities">; voiceId: string },
VoiceCloneError | InsufficientContentError,
AICloneService
> => { ... }
// ❌ WRONG: Implicit or any types
export const createClone = (creatorId: any) => { ... }Never use any unless:
- Properties field in entities (stores arbitrary JSON)
- Interfacing with untyped external APIs
- Even then, add comment explaining why
// ✅ CORRECT: Effect.ts service
export class TokenService extends Effect.Service<TokenService>()(
"TokenService",
{
effect: Effect.gen(function* () {
const db = yield* ConvexDatabase;
const stripe = yield* StripeProvider;
return {
purchaseTokens: (args) =>
Effect.gen(function* () {
// Typed errors, automatic rollback, composable
const payment = yield* stripe.charge(args.amount);
const tokens = yield* blockchain.mint(args.tokens);
return { payment, tokens };
})
};
}),
dependencies: [ConvexDatabase.Default, StripeProvider.Default]
}
) {}
// ❌ WRONG: Raw async/await with try/catch
export const purchaseTokens = async (args) => {
try {
const payment = await stripe.charge(args.amount);
// Silent failures, no rollback, hard to test
} catch (e) {
// Lost type information
}
}// ✅ CORRECT: Thin wrapper calling Effect.ts service
export const purchaseTokens = confect.mutation({
args: { userId: v.id("entities"), amount: v.number() },
handler: (ctx, args) =>
Effect.gen(function* () {
const tokenService = yield* TokenService;
return yield* tokenService.purchaseTokens(args);
}).pipe(Effect.provide(MainLayer))
});
// ❌ WRONG: Business logic in Convex function
export const purchaseTokens = mutation({
handler: async (ctx, args) => {
// Lots of business logic here - should be in service!
}
});// ✅ CORRECT: Convex hooks + shadcn
import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
import { Button } from "@/components/ui/button";
export function TokenPurchase({ tokenId }: { tokenId: Id<"entities"> }) {
const balance = useQuery(api.tokens.getBalance, { tokenId });
const purchase = useMutation(api.tokens.purchaseTokens);
return (
<Button
onClick={() => purchase({ tokenId, amount: 100 })}
disabled={balance === undefined}
>
Purchase 100 Tokens
</Button>
);
}
// ❌ WRONG: Direct fetch or manual state
export function TokenPurchase() {
const [balance, setBalance] = useState(null);
useEffect(() => {
fetch("/api/balance").then(r => setBalance(r.json()));
}, []);
}// ✅ CORRECT: SSR with Convex client
---
import { ConvexHttpClient } from "convex/browser";
import { api } from "@/convex/_generated/api";
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 });
---
<Layout>
<h1>{token.name}</h1>
<TokenPurchase client:load tokenId={token._id} />
</Layout>
// ❌ WRONG: Client-side only fetching
---
import TokenPurchase from "@/components/features/tokens/TokenPurchase";
---
<TokenPurchase client:load /> <!-- No SSR data -->✅ CORRECT:
src/components/features/tokens/TokenPurchase.tsx
convex/services/tokens/purchase.ts
convex/mutations/tokens.ts
tests/integration/token-purchase.test.ts
❌ WRONG:
src/components/TokenPurchaseComponent.tsx (redundant "Component")
convex/tokenService.ts (not in services/ folder)
convex/mutations/token-mutations.ts (redundant "mutations")
tests/tokenPurchase.test.ts (not organized by type)
// ✅ CORRECT: Tagged error classes
export class InsufficientTokensError {
readonly _tag = "InsufficientTokensError";
constructor(
readonly userId: Id<"entities">,
readonly required: number,
readonly available: number
) {}
}
// Usage
Effect.catchTag("InsufficientTokensError", (error) => {
// TypeScript knows error.required, error.available exist
return Effect.succeed({ message: "Buy more tokens" });
})
// ❌ WRONG: Generic errors
throw new Error("Insufficient tokens");// ✅ CORRECT: Test defines expected behavior
describe("TokenService.purchaseTokens", () => {
it("should mint tokens and charge payment atomically", async () => {
const result = await Effect.runPromise(
tokenService.purchaseTokens({
userId: "user-123",
amount: 100,
usdAmount: 10
}).pipe(Effect.provide(TestLayer))
);
expect(result.tokens).toBe(100);
expect(mockStripe.charges).toHaveLength(1);
expect(mockBlockchain.minted).toBe(100);
});
it("should rollback on payment failure", async () => {
mockStripe.shouldFail = true;
await expect(
Effect.runPromise(purchase.pipe(Effect.provide(TestLayer)))
).rejects.toThrow("PaymentFailedError");
expect(mockBlockchain.minted).toBe(0); // Rolled back
});
});
// ❌ WRONG: Test without clear assertions
it("works", async () => {
await purchaseTokens({ userId: "123", amount: 100 });
// No assertions about what "works" means
});// ✅ CORRECT: JSDoc with examples
/**
* Purchases tokens for a user, charging their payment method and minting
* tokens on the blockchain atomically.
*
* @example
* ```typescript
* const result = yield* tokenService.purchaseTokens({
* userId: "user-123",
* amount: 100,
* usdAmount: 10
* });
* ```
*
* @throws {InsufficientTokensError} When user tries to buy more than available
* @throws {PaymentFailedError} When Stripe payment fails
* @throws {BlockchainError} When minting fails
*/
purchaseTokens: (args: PurchaseArgs) => Effect.Effect<...>
// ❌ WRONG: No documentation
purchaseTokens: (args) => { ... }Responsibilities:
- Generate React components (TSX files)
- Create Astro pages
- Use shadcn/ui components
- Integrate Convex hooks (useQuery, useMutation)
- Handle loading/error states
- Responsive design with Tailwind
Files you can modify:
src/components/**/*src/pages/**/*src/layouts/**/*src/styles/**/*src/lib/hooks.ts
Files you CANNOT modify:
convex/**/*(backend agent's domain)
Read before generating:
.ai/specs/[feature].md- Feature specconvex/_generated/api.d.ts- Available APIssrc/components/ui/*- Available shadcn components
Responsibilities:
- Generate Convex schema changes
- Create Effect.ts services
- Write mutations/queries/actions
- Implement workflows
- Handle external API integrations
Files you can modify:
convex/**/*
Files you CANNOT modify:
src/**/*(frontend agent's domain)
Read before generating:
.ai/specs/[feature].md- Feature spec.ai/context/ontology.md- Data model rulesconvex/schema/types.ts- Entity property types
Responsibilities:
- Migrate data from old sites (one.ie, bullfm)
- Transform data to 6-dimension ontology
- Preserve relationships
- Generate migration reports
Files you can modify:
scripts/migration/**/*
Read before generating:
- Old database schema
.ai/context/ontology.md- Target schema- Transformation rules
Responsibilities:
- Generate unit tests for services
- Generate integration tests for workflows
- Generate E2E tests for user flows
- Mock services using Effect.ts Layer
Files you can modify:
tests/**/*
Read before generating:
- Implementation files
.ai/specs/[feature].md- Expected behavior- Existing test patterns
Effect.gen(function* () {
// 1. Create main entity
const entityId = yield* Effect.tryPromise(() =>
db.insert("entities", {
type: "course",
name: args.title,
properties: { /* ... */ },
status: "draft",
createdAt: Date.now(),
updatedAt: Date.now(),
})
);
// 2. Create ownership relationship
yield* Effect.tryPromise(() =>
db.insert("connections", {
fromEntityId: args.creatorId,
toEntityId: entityId,
relationshipType: "owns",
createdAt: Date.now(),
})
);
// 3. Log creation event
yield* Effect.tryPromise(() =>
db.insert("events", {
entityId,
eventType: "course_created",
actorId: args.creatorId,
timestamp: Date.now(),
actorType: "user",
metadata: { /* ... */ },
})
);
return entityId;
})// Get all entities of type X owned by user Y
const courses = yield* Effect.tryPromise(() =>
db.query("connections")
.withIndex("from_type", q =>
q.eq("fromEntityId", userId)
.eq("relationshipType", "owns")
)
.collect()
.then(conns =>
Promise.all(
conns
.filter(c => c.toEntity.type === "course")
.map(c => c.toEntity)
)
)
);const result = yield* Effect.all(
[
operation1(),
operation2(),
operation3(),
],
{ concurrency: 3 }
).pipe(
Effect.tap(([r1, r2, r3]) =>
// All succeeded, save to DB
Effect.tryPromise(() => db.insert("events", { actorId: userId, /* ... */ }))
),
Effect.onError(() =>
// Any failed, rollback all
Effect.all([
rollback1(),
rollback2(),
rollback3(),
])
)
);import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
export function FeatureComponent({ id }: { id: Id<"entities"> }) {
const data = useQuery(api.feature.get, { id });
const update = useMutation(api.feature.update);
if (data === undefined) {
return <Skeleton />; // Loading state
}
if (data === null) {
return <Alert>Not found</Alert>; // Error state
}
return (
<Card>
<CardHeader>
<CardTitle>{data.name}</CardTitle>
</CardHeader>
<CardContent>
<Button onClick={() => update({ id, /* ... */ })}>
Update
</Button>
</CardContent>
</Card>
);
}Before generating ANY code, verify:
- I have read
.ai/rules.md(this file) - I have read the relevant spec in
.ai/specs/ - I have read
.ai/context/ontology.md - I understand which entities/connections/events are involved
- I know which agent I am (frontend/backend/test/ingestor)
- I am only modifying files in my domain
- I will use Effect.ts for business logic
- I will use explicit types everywhere
- I will write tests that define behavior
- I will update documentation after generating code
After generating code, verify:
- TypeScript compiles with no errors (
bunx astro check) - Tests pass (
bun test) - Code follows patterns in
.ai/context/patterns.md - No
anytypes except inpropertiesfields - All errors are typed classes with
_tag - React components have loading/error states
- Convex functions are thin wrappers around services
- File is in correct location per
.ai/context/file-map.md - Updated
.ai/context/file-map.mdif new files created - Added JSDoc comments with examples
Typical codebase problems:
- No clear data model → AI doesn't know where to put things
- Mixed concerns → AI breaks things unintentionally
- Implicit types → AI generates buggy code
- No composition → AI duplicates logic
- No tests → AI doesn't know if it broke something
ONE platform solutions:
- 6-dimension ontology → AI always knows data structure
- Agent specialization → AI stays in its lane
- Explicit types → AI catches errors at compile time
- Effect.ts services → AI composes existing functions
- Test-driven → AI validates behavior automatically
The result:
- Code quality INCREASES with codebase size
- Later features are EASIER than early features
- Refactoring is SAFE because types + tests catch breaks
- AI agents get SMARTER because they learn patterns
If you (AI agent) are unsure about ANYTHING:
- STOP generating code
- ASK the human: "I need clarification on [specific thing]"
- READ more context: Re-read specs and ontology
- SEARCH examples: Look for similar patterns in existing code
- SIMPLIFY: Start with simplest possible solution
NEVER:
- Generate code you're not confident about
- Use
anybecause you don't know the type - Copy-paste without understanding
- Modify files outside your agent domain
- Skip writing tests
This file is versioned. Breaking changes require major version bump.
Current: 1.0.0
When updating:
- Document what changed
- Update all affected specs
- Regenerate affected code
- Update tests
END OF GOLDEN RULES
If you're an AI agent reading this: You now have the foundation to build production-quality code that makes the ONE platform better with every feature. Read the ontology next, then the specific feature spec you're implementing.
If you're a human reading this: Your AI agents now have clear, unambiguous rules. They will generate consistent, high-quality code because they understand the system architecture deeply.