Core Types
All types are exported from @stoneforge/core. Every data structure in Stoneforge is an element — tasks, entities, documents, messages, plans, workflows, channels, libraries, teams, and playbooks all share a common base.
Element
The base type shared by all elements:
interface Element { id: ElementId; type: ElementType; createdAt: Timestamp; // ISO 8601 updatedAt: Timestamp; // ISO 8601 createdBy: EntityId; tags: string[]; metadata: Record<string, unknown>; // Arbitrary JSON (64KB limit) deletedAt?: Timestamp; // Soft-delete timestamp}
type ElementType = | 'task' | 'entity' | 'document' | 'message' | 'plan' | 'workflow' | 'playbook' | 'channel' | 'library' | 'team';Type guards and utilities:
import { isElement, generateId } from '@stoneforge/core';
isElement(value); // Type guardgenerateId(input); // Generate hash-based IDTask
The primary work unit. Tasks track what needs to be done, who’s doing it, and what’s blocking progress.
interface Task extends Element { type: 'task'; title: string; descriptionRef?: DocumentId; acceptanceCriteria?: string; status: TaskStatus; priority: Priority; // 1 (critical) to 5 (minimal) complexity: Complexity; // 1 (simplest) to 5 (most complex) taskType: TaskTypeValue; closeReason?: string; assignee?: EntityId; owner?: EntityId; deadline?: Timestamp; scheduledFor?: Timestamp; closedAt?: Timestamp; externalRef?: string; deletedAt?: Timestamp; deletedBy?: EntityId; deleteReason?: string;}TaskStatus
type TaskStatus = | 'open' | 'in_progress' | 'blocked' // Computed from dependencies | 'deferred' | 'backlog' | 'review' | 'closed' | 'tombstone'; // Terminal — soft-deletedStatus transitions
| From | Allowed transitions |
|---|---|
open | in_progress, blocked, deferred, backlog, closed |
in_progress | open, blocked, deferred, review, closed |
blocked | open, in_progress, deferred, closed |
deferred | open, in_progress, backlog |
backlog | open, deferred, closed |
review | closed, in_progress, open |
closed | open only |
tombstone | Terminal — no transitions |
Priority & complexity
type Priority = 1 | 2 | 3 | 4 | 5;// 1 = critical, 2 = high, 3 = medium, 4 = low, 5 = minimal
type Complexity = 1 | 2 | 3 | 4 | 5;// 1 = trivial, 2 = simple, 3 = moderate, 4 = complex, 5 = very complexTaskType
const TaskTypeValue = { BUG: 'bug', FEATURE: 'feature', TASK: 'task', CHORE: 'chore',} as const;Utilities
import { createTask, updateTaskStatus, softDeleteTask, isTask } from '@stoneforge/core';
await createTask(input); // Async factory (returns Promise<Task>)await createTask(input, config); // With optional IdGeneratorConfigupdateTaskStatus(task, { status, closeReason? }); // Validated transitionsoftDeleteTask(task, { deletedBy, deleteReason? }); // Soft-delete (sets tombstone)isTask(element); // Type guardEntity
Represents any actor in the system — a human, an AI agent, or a system process.
interface Entity extends Element { type: 'entity'; name: string; // Unique, case-sensitive entityType: EntityTypeValue; reportsTo?: EntityId; publicKey?: string; // Ed25519 public key (for cryptographic mode)}
const EntityTypeValue = { AGENT: 'agent', HUMAN: 'human', SYSTEM: 'system',} as const;Constraints
- Names must match:
/^[a-zA-Z][a-zA-Z0-9_-]*$/ - Reserved names (case-insensitive):
system,anonymous,unknown
Utilities
import { createEntity, isEntity, validateEntity } from '@stoneforge/core';Document
Versioned content with full-text search support. Used for task descriptions, message content, knowledge base articles, and more.
interface Document extends Element { type: 'document'; title?: string; contentType: ContentType; content: string; version: number; // Starts at 1 previousVersionId: DocumentId | null; // null for version 1 category: DocumentCategory; status: DocumentStatus; immutable: boolean;}
type ContentType = 'text' | 'markdown' | 'json';type DocumentStatus = 'active' | 'archived';DocumentCategory
type DocumentCategory = | 'spec' // Technical specifications | 'prd' // Product requirement documents | 'decision-log' // Architecture/design decisions | 'changelog' // Release and change notes | 'tutorial' // Learning-oriented walkthroughs | 'how-to' // Goal-oriented instructions | 'explanation' // Conceptual discussion | 'reference' // Technical reference | 'runbook' // Operational procedures | 'meeting-notes' // Meeting summaries | 'post-mortem' // Incident analysis | 'task-description' // System-managed | 'message-content' // System-managed | 'other'; // DefaultConstraints
- Content size limited to 10 MB (UTF-8 bytes)
categoryandstatusare required (defaults applied at creation)task-descriptionandmessage-contentare system-managed categories- Archived documents are hidden from default list/search results
- When
immutableistrue, content updates are rejected - Documents with
message-contentcategory are automatically set toimmutable: true
Utilities
import { createDocument, isDocument, validateJsonContent } from '@stoneforge/core';Message
Immutable communication records between entities.
interface Message extends Element { type: 'message'; sender: EntityId; channelId: ChannelId; threadId: MessageId | null; // null for non-threaded contentRef: DocumentId; // Reference to content Document attachments: readonly DocumentId[];}Utilities
import { createMessage, isMessage } from '@stoneforge/core';Dependency
Relationships between elements. Dependencies power blocking, ordering, threading, attribution, and more.
interface Dependency { blockedId: ElementId; blockerId: ElementId; type: DependencyType; createdAt: Timestamp; createdBy: EntityId; metadata: Record<string, unknown>;}DependencyType
type DependencyType = // Blocking (trigger blocked status) | 'blocks' // blockedId waits for blockerId | 'parent-child' // blockedId (child) waits for blockerId (parent) | 'awaits' // blockedId waits for gate on blockerId // Associative | 'relates-to' // Bidirectional association | 'references' // Citation | 'supersedes' // Replacement | 'duplicates' // Duplicate of | 'caused-by' // Causal link | 'validates' // Validates another element | 'mentions' // @mention reference // Attribution | 'authored-by' | 'assigned-to' | 'approved-by' // Threading | 'replies-to';Direction semantics
| Type | Who waits |
|---|---|
blocks | blockedId waits for blockerId |
parent-child | blockedId (child) waits for blockerId (parent) |
awaits | blockedId waits for gate on blockerId |
relates-to | Neither (bidirectional) |
Gate metadata
The awaits dependency type supports gates — conditions that must be satisfied before the dependency is resolved:
// Timer gate{ gateType: 'timer', waitUntil: Timestamp }
// Approval gate{ gateType: 'approval', requiredApprovers: EntityId[], currentApprovers?: EntityId[] }
// External gate{ gateType: 'external', externalSystem: string, externalId: string, satisfied?: boolean }
// Webhook gate{ gateType: 'webhook', webhookUrl?: string, callbackId?: string, satisfied?: boolean }Plan
Groups related tasks. Plans start as draft — tasks in draft plans are not dispatchable.
interface Plan extends Element { type: 'plan'; title: string; descriptionRef?: DocumentId; status: PlanStatus; completedAt?: Timestamp; cancelledAt?: Timestamp; cancelReason?: string;}
type PlanStatus = 'draft' | 'active' | 'completed' | 'cancelled';Tasks are added to plans via parent-child dependencies. Tasks in a plan are not blocked by plan status — they are only blocked by task-level dependencies.
Workflow
Instantiated from playbooks. Reusable task sequences with durable state.
interface Workflow extends Element { type: 'workflow'; title: string; descriptionRef?: DocumentId; status: WorkflowStatus; playbookId?: PlaybookId; ephemeral: boolean; variables: Record<string, unknown>; startedAt?: Timestamp; finishedAt?: Timestamp; failureReason?: string; cancelReason?: string;}
type WorkflowStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';Channel
Communication channels between entities.
interface Channel extends Element { type: 'channel'; name: string; description: string | null; channelType: ChannelType; members: EntityId[]; permissions: ChannelPermissions;}
type ChannelType = 'direct' | 'group';
interface ChannelPermissions { visibility: 'public' | 'private'; joinPolicy: 'open' | 'invite-only' | 'request'; modifyMembers: EntityId[];}Direct channel names are deterministic: entityA:entityB (sorted alphabetically).
Library
Hierarchical collections of documents.
interface Library extends Element { type: 'library'; name: string; descriptionRef?: DocumentId;}Documents and sub-libraries are linked via parent-child dependencies. Circular nesting is prevented at the API level.
Team
interface Team extends Element { type: 'team'; name: string; members: EntityId[]; descriptionRef?: DocumentId; status?: TeamStatus; deletedAt?: Timestamp; deletedBy?: EntityId; deleteReason?: string;}
type TeamStatus = 'active' | 'tombstone';Playbook
Templates for workflows. Instantiated with variable substitution.
interface Playbook extends Element { type: 'playbook'; name: string; title: string; descriptionRef?: DocumentId; version: number; steps: PlaybookStep[]; variables: PlaybookVariable[]; extends?: string[]; // Parent playbook names}Variable substitution uses {{varName}} syntax.
InboxItem
Notification items delivered to entity inboxes.
interface InboxItem { id: string; recipientId: EntityId; messageId: MessageId; channelId: ChannelId; sourceType: InboxSourceType; status: InboxStatus; readAt: Timestamp | null; // null if archived without reading createdAt: Timestamp;}
type InboxStatus = 'unread' | 'read' | 'archived';type InboxSourceType = 'direct' | 'mention' | 'thread_reply';Event
Audit trail entries. Every mutation generates an event.
interface Event { id: number; // Auto-incrementing elementId: ElementId; eventType: EventType; actor: EntityId; oldValue: Record<string, unknown> | null; newValue: Record<string, unknown> | null; createdAt: Timestamp;}
type EventType = | 'created' | 'updated' | 'closed' | 'reopened' | 'deleted' | 'dependency_added' | 'dependency_removed' | 'tag_added' | 'tag_removed' | 'member_added' | 'member_removed' | 'comment_added' | 'comment_updated' | 'comment_deleted' | 'comment_resolved' | 'comment_unresolved' | 'auto_blocked' | 'auto_unblocked';Auto-generated blocking events use actor 'system:blocked-cache'.
Branded ID types
Stoneforge uses branded types to prevent accidentally mixing up IDs from different element types:
type ElementId = string & { readonly [ElementIdBrand]: typeof ElementIdBrand };type EntityId = string & { readonly [EntityIdBrand]: typeof EntityIdBrand };type DocumentId = ElementId & { readonly [DocumentIdBrand]: typeof DocumentIdBrand };type MessageId = ElementId & { readonly [MessageIdBrand]: typeof MessageIdBrand };type ChannelId = ElementId & { readonly [ChannelIdBrand]: typeof ChannelIdBrand };type WorkflowId = ElementId & { readonly [WorkflowIdBrand]: typeof WorkflowIdBrand };type TeamId = ElementId & { readonly [TeamIdBrand]: typeof TeamIdBrand };type LibraryId = ElementId & { readonly [LibraryIdBrand]: typeof LibraryIdBrand };type PlaybookId = ElementId & { readonly [PlaybookIdBrand]: typeof PlaybookIdBrand };Cast utilities
At trust boundaries (API responses, database rows, config values), use cast utilities instead of as unknown as:
import { asEntityId, asElementId } from '@stoneforge/core';
const entityId = asEntityId('ent-abc123');const elementId = asElementId('el-xyz789');Error types
Errors are organized by category with structured error codes:
// Base classclass StoneforgeError extends Error { code: string; details?: Record<string, unknown>; httpStatus: number;}
// Subclassesclass ValidationError extends StoneforgeError {} // Input validationclass NotFoundError extends StoneforgeError {} // Element/entity not foundclass ConflictError extends StoneforgeError {} // Duplicate, cycleclass ConstraintError extends StoneforgeError {} // Immutability, structuralclass StorageError extends StoneforgeError {} // Database errorsclass IdentityError extends StoneforgeError {} // Auth/signature errorsError codes
| Category | Codes |
|---|---|
| Validation | INVALID_INPUT, INVALID_ID, INVALID_STATUS, TITLE_TOO_LONG, INVALID_CONTENT_TYPE, INVALID_JSON, MISSING_REQUIRED_FIELD, INVALID_TAG, INVALID_TIMESTAMP, INVALID_METADATA, INVALID_CATEGORY, INVALID_DOCUMENT_STATUS |
| Not Found | NOT_FOUND, ENTITY_NOT_FOUND, DOCUMENT_NOT_FOUND, CHANNEL_NOT_FOUND, PLAYBOOK_NOT_FOUND, DEPENDENCY_NOT_FOUND |
| Conflict | ALREADY_EXISTS, DUPLICATE_NAME, CYCLE_DETECTED, SYNC_CONFLICT, DUPLICATE_DEPENDENCY, CONCURRENT_MODIFICATION |
| Constraint | IMMUTABLE, HAS_DEPENDENTS, INVALID_PARENT, MAX_DEPTH_EXCEEDED, MEMBER_REQUIRED, TYPE_MISMATCH, ALREADY_IN_PLAN |
| Storage | DATABASE_ERROR, DATABASE_BUSY, EXPORT_FAILED, IMPORT_FAILED, MIGRATION_FAILED |
| Identity | INVALID_SIGNATURE, SIGNATURE_VERIFICATION_FAILED, SIGNATURE_EXPIRED, INVALID_PUBLIC_KEY, ACTOR_NOT_FOUND, SIGNATURE_REQUIRED, NO_PUBLIC_KEY |
import { ErrorCode, StoneforgeError } from '@stoneforge/core';
try { await api.create({ /* ... */ });} catch (err) { if (err instanceof StoneforgeError) { switch (err.code) { case ErrorCode.DUPLICATE_NAME: // Handle duplicate break; case ErrorCode.CYCLE_DETECTED: // Handle dependency cycle break; } }}