Add architecture document and RT analysis reference
This commit is contained in:
310
docs/architecture.md
Normal file
310
docs/architecture.md
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
# Tessera Architecture
|
||||||
|
|
||||||
|
## Principle
|
||||||
|
|
||||||
|
Tessera is RT's paradigm rebuilt: a ticketing system that makes zero assumptions about your workflow. It provides composable primitives — scrips, custom fields, lifecycles, transactions — and you build your process on top.
|
||||||
|
|
||||||
|
Everything is a transaction. Everything is queryable. Everything is automatable.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
- **Config > Code**: Scrips are defined in a web UI, not in source files. The scrip runtime is sandboxed, the conditions and actions are configurable from data.
|
||||||
|
- **Prepare/Commit**: Every state change goes through a two-phase dispatch that can be dry-run. No action executes without the user knowing what will happen.
|
||||||
|
- **SystemUser**: Automation runs elevated. The system acts on behalf of the configuration, not the triggering user's permissions.
|
||||||
|
- **Single Binary**: The core (Rust) ships as one binary. Integration plugins (Go) are sidecar processes over gRPC.
|
||||||
|
- **PostgreSQL**: No ORM. Raw SQL with compile-time query building. Query builder compiles to SQL, not to an ORM abstraction.
|
||||||
|
|
||||||
|
## MVP Scope — "ScripFoundry"
|
||||||
|
|
||||||
|
The MVP is the scrip runtime + enough scaffolding to exercise it. This is the wedge: nobody else has a configurable event-driven automation engine with prepare/commit and a web UI for defining rules.
|
||||||
|
|
||||||
|
### What's In
|
||||||
|
|
||||||
|
1. **Ticket model** — id, subject, queue_id, status, owner_id, creator_id, created_at, updated_at
|
||||||
|
2. **Queue model** — id, name, description, lifecycle_id
|
||||||
|
3. **Transaction model** — id, ticket_id, transaction_type, field, old_value, new_value, creator_id, created_at
|
||||||
|
4. **Lifecycle state machine** — per-queue status definitions, transitions, transition-gating rights. Hardcoded "default" lifecycle for MVP.
|
||||||
|
5. **Scrip engine** (the crown jewel):
|
||||||
|
- Define conditions (OnCreate, OnStatusChange, OnResolve, UserDefined)
|
||||||
|
- Define actions (SendEmail, SetCustomField, Webhook)
|
||||||
|
- Define templates (variable substitution: `{$ticket.id}`, `{$ticket.subject}`, etc.)
|
||||||
|
- Prepare/Commit dispatch with sort ordering
|
||||||
|
- TransactionCreate and TransactionBatch stages
|
||||||
|
- Dry-run mode: /api/tickets/:id/preview returns "what will happen if I apply this update"
|
||||||
|
6. **Custom Fields (MVP subset)**:
|
||||||
|
- Freeform text, Select single, Select multiple
|
||||||
|
- Attached to tickets
|
||||||
|
- Queryable
|
||||||
|
- Appear in variable substitution
|
||||||
|
7. **REST API** — CRUD for tickets, queues, transactions, scrips, custom fields. The UI is a separate concern.
|
||||||
|
8. **Email actions** — SMTP send via scrip action. Template-driven subject/body.
|
||||||
|
9. **Webhook actions** — HTTP POST on scrip trigger. Template-driven URL/body/headers.
|
||||||
|
|
||||||
|
### What's Out (Post-MVP)
|
||||||
|
|
||||||
|
- User management (hardcode admin user)
|
||||||
|
- Authentication/authorization (trust boundary not in MVP)
|
||||||
|
- Full ACL system
|
||||||
|
- Transaction query builder UI (API-only)
|
||||||
|
- Multi-tenancy
|
||||||
|
- Migration/import tools
|
||||||
|
- Attachments
|
||||||
|
- Real email ingestion (only outbound for MVP)
|
||||||
|
- Notifications beyond email and webhooks (no Slack, SMS, etc.)
|
||||||
|
- Admin web UI (API-driven, build later)
|
||||||
|
- Custom scrip code execution (UserDefined is config-based, not code-based in MVP)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Component Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ tessera-core (Rust) │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
|
||||||
|
│ │ HTTP API │ │ Scrip │ │ Lifecycle │ │
|
||||||
|
│ │ (axum) │ │ Engine │ │ State Machine │ │
|
||||||
|
│ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ┌────┴────────────┴───────────────┴──────────┐ │
|
||||||
|
│ │ Query Builder │ │
|
||||||
|
│ │ (SQL generation from typed filter ASTs) │ │
|
||||||
|
│ └────────────────────┬───────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌────────────────────┴───────────────────────┐ │
|
||||||
|
│ │ PostgreSQL (sqlx) │ │
|
||||||
|
│ └────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────────────────────────┐ │
|
||||||
|
│ │ gRPC Server (tonic) │ │
|
||||||
|
│ └────────────────┬───────────────────────────┘ │
|
||||||
|
└───────────────────┼────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌───────────┼───────────┐
|
||||||
|
│ │ │
|
||||||
|
┌───────┴───┐ ┌─────┴─────┐ ┌──┴──────────┐
|
||||||
|
│ Email │ │ Webhook │ │ Custom ... │
|
||||||
|
│ Connector│ │ Connector│ │ Connectors │
|
||||||
|
│ (Go) │ │ (Go) │ │ (Go) │
|
||||||
|
└───────────┘ └───────────┘ └──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Technology Decisions
|
||||||
|
|
||||||
|
| Layer | Technology | Rationale |
|
||||||
|
|-------|-----------|-----------|
|
||||||
|
| HTTP framework | axum | Ergonomic, tower-based middleware, best-in-class Rust HTTP |
|
||||||
|
| Database | PostgreSQL 15+ | Transactional DDL, CTEs, JSONB, full-text search |
|
||||||
|
| SQL driver | sqlx | Compile-time checked queries, async, migrations |
|
||||||
|
| gRPC | tonic/prost | Well-typed contracts between Rust core and Go integrations |
|
||||||
|
| Scrip sandbox | wasmtime | Execute user-defined scrip conditions/actions in WASM sandbox |
|
||||||
|
| Templating | Tera | Django-like syntax, variable interpolation |
|
||||||
|
| Serialization | serde + serde_json | Standard |
|
||||||
|
| Logging | tracing | Structured, async-aware |
|
||||||
|
| Config | figment | Hierarchical config (file + env + defaults) |
|
||||||
|
| Integration runtime | Go 1.22+ | Single binary, excellent gRPC support, ops ecosystem |
|
||||||
|
|
||||||
|
### Data Model (Core Tables)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Tickets
|
||||||
|
CREATE TABLE tickets (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
subject TEXT NOT NULL,
|
||||||
|
queue_id UUID NOT NULL REFERENCES queues(id),
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
owner_id UUID REFERENCES users(id),
|
||||||
|
creator_id UUID NOT NULL REFERENCES users(id),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
started_at TIMESTAMPTZ,
|
||||||
|
resolved_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Queues
|
||||||
|
CREATE TABLE queues (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
lifecycle_id UUID REFERENCES lifecycles(id),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Lifecycles
|
||||||
|
CREATE TABLE lifecycles (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
definition JSONB NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Transactions (the universal event log)
|
||||||
|
CREATE TABLE transactions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
ticket_id UUID NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
|
||||||
|
transaction_type TEXT NOT NULL, -- 'Create', 'StatusChange', 'Correspond', 'Comment', 'CustomField', 'EmailRecord', etc.
|
||||||
|
field TEXT,
|
||||||
|
old_value TEXT,
|
||||||
|
new_value TEXT,
|
||||||
|
data JSONB,
|
||||||
|
creator_id UUID NOT NULL REFERENCES users(id),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Scrips
|
||||||
|
CREATE TABLE scrips (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
queue_id UUID REFERENCES queues(id), -- NULL = global
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
condition_type TEXT NOT NULL, -- 'OnCreate', 'OnStatusChange', etc.
|
||||||
|
condition_config JSONB NOT NULL DEFAULT '{}',
|
||||||
|
action_type TEXT NOT NULL, -- 'SendEmail', 'SetCustomField', 'Webhook'
|
||||||
|
action_config JSONB NOT NULL DEFAULT '{}',
|
||||||
|
template_id UUID REFERENCES templates(id),
|
||||||
|
stage TEXT NOT NULL DEFAULT 'TransactionCreate', -- 'TransactionCreate' or 'TransactionBatch'
|
||||||
|
sort_order INT NOT NULL DEFAULT 0,
|
||||||
|
disabled BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Templates
|
||||||
|
CREATE TABLE templates (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
queue_id UUID REFERENCES queues(id), -- NULL = global
|
||||||
|
subject_template TEXT NOT NULL,
|
||||||
|
body_template TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
UNIQUE(name, queue_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Custom Fields
|
||||||
|
CREATE TABLE custom_fields (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
field_type TEXT NOT NULL, -- 'Freeform', 'SelectSingle', 'SelectMulti'
|
||||||
|
values JSONB, -- For select types: ["opt1", "opt2"]
|
||||||
|
max_values INT NOT NULL DEFAULT 1, -- 0 = unlimited
|
||||||
|
pattern TEXT, -- Validation regex
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Custom Field <-> Queue attachment
|
||||||
|
CREATE TABLE queue_custom_fields (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
queue_id UUID REFERENCES queues(id),
|
||||||
|
custom_field_id UUID REFERENCES custom_fields(id) ON DELETE CASCADE,
|
||||||
|
sort_order INT NOT NULL DEFAULT 0,
|
||||||
|
UNIQUE(queue_id, custom_field_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Custom Field Values (on tickets)
|
||||||
|
CREATE TABLE custom_field_values (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
custom_field_id UUID NOT NULL REFERENCES custom_fields(id) ON DELETE CASCADE,
|
||||||
|
ticket_id UUID NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
UNIQUE(custom_field_id, ticket_id, value)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Users (MVP: minimal)
|
||||||
|
CREATE TABLE users (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
email TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Design (MVP Endpoints)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/health
|
||||||
|
GET /api/tickets
|
||||||
|
POST /api/tickets
|
||||||
|
GET /api/tickets/:id
|
||||||
|
PATCH /api/tickets/:id
|
||||||
|
POST /api/tickets/:id/preview -- Dry-run: returns scrips that would fire
|
||||||
|
GET /api/tickets/:id/transactions
|
||||||
|
POST /api/tickets/:id/comment -- Add comment (creates transaction)
|
||||||
|
GET /api/queues
|
||||||
|
POST /api/queues
|
||||||
|
GET /api/scrips
|
||||||
|
POST /api/scrips
|
||||||
|
GET /api/scrips/:id
|
||||||
|
PATCH /api/scrips/:id
|
||||||
|
GET /api/custom-fields
|
||||||
|
POST /api/custom-fields
|
||||||
|
GET /api/lifecycles
|
||||||
|
POST /api/lifecycles
|
||||||
|
POST /api/tickets/search -- TicketSQL query body
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scrip Engine Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Ticket update request arrives → axum handler
|
||||||
|
2. Validate transition (lifecycle state machine)
|
||||||
|
3. Apply update → creates Transaction record(s)
|
||||||
|
4. Scrip Engine triggered:
|
||||||
|
a. Load matching scrips (global + queue-specific)
|
||||||
|
b. Filter by ApplicableTransTypes matching transaction type
|
||||||
|
c. Sort by sort_order
|
||||||
|
d. PREPARE PHASE: For each scrip:
|
||||||
|
- Evaluate condition (no side effects)
|
||||||
|
- If condition matches, build action payload with template substitution
|
||||||
|
- Push to prepared_scrips
|
||||||
|
e. COMMIT PHASE: For each prepared scrip:
|
||||||
|
- Execute action (send email, fire webhook, set CF, etc.)
|
||||||
|
- Create EmailRecord or WebhookDelivery transaction on success
|
||||||
|
5. Return response: { ticket, transactions, scrips_fired: [...] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
tessera/
|
||||||
|
├── Cargo.toml
|
||||||
|
├── README.md
|
||||||
|
├── docs/
|
||||||
|
│ ├── architecture.md (this file)
|
||||||
|
│ └── rt-analysis.md (RT deep-dive reference)
|
||||||
|
├── crates/
|
||||||
|
│ ├── tessera-core/ Main library: models, scrip engine, lifecycle
|
||||||
|
│ │ ├── Cargo.toml
|
||||||
|
│ │ └── src/
|
||||||
|
│ │ ├── lib.rs
|
||||||
|
│ │ ├── models/ ticket, queue, transaction, scrip, etc.
|
||||||
|
│ │ ├── scrip/ engine, conditions, actions, templates
|
||||||
|
│ │ ├── lifecycle/ state machine, transition validation
|
||||||
|
│ │ ├── query/ query builder, TicketSQL AST
|
||||||
|
│ │ └── db/ sqlx queries, migrations
|
||||||
|
│ ├── tessera-api/ HTTP API server (axum)
|
||||||
|
│ │ ├── Cargo.toml
|
||||||
|
│ │ └── src/
|
||||||
|
│ │ ├── main.rs
|
||||||
|
│ │ ├── routes/ ticket, queue, scrip, lifecycle, search
|
||||||
|
│ │ └── middleware/ logging, error handling
|
||||||
|
│ └── tessera-grpc/ gRPC server for integration connectors
|
||||||
|
│ ├── Cargo.toml
|
||||||
|
│ └── src/
|
||||||
|
├── proto/ Protobuf definitions
|
||||||
|
│ └── tessera/
|
||||||
|
│ └── v1/
|
||||||
|
│ ├── tickets.proto
|
||||||
|
│ ├── scrips.proto
|
||||||
|
│ └── actions.proto
|
||||||
|
├── integrations/ Go integration connectors
|
||||||
|
│ ├── go.mod
|
||||||
|
│ ├── cmd/
|
||||||
|
│ │ ├── email-connector/
|
||||||
|
│ │ └── webhook-connector/
|
||||||
|
│ └── pkg/
|
||||||
|
│ └── tessera/ Generated gRPC client
|
||||||
|
├── migrations/ SQLx migrations
|
||||||
|
└── scripts/ Dev tooling
|
||||||
|
└── dev-setup.sh
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user