---
title: "13: Events and CI | SurrealDB University"
description: "DEFINE EVENT rules for the planning schema and surrealkit test for automated checks."
url: https://surrealdb.com/learn/schemas/page-13
---

![Course content preview](https://surrealdb.com/assets/static/course-schemas.D4CFbBhP.avif)

Course chapters

[Back to courses](https://surrealdb.com/learn) [Schema internals and migrations](https://surrealdb.com/learn/schemas) Internals [1: Schemaless vs. schemafull](https://surrealdb.com/learn/schemas/page-01) [2: Schema internals](https://surrealdb.com/learn/schemas/page-02) [3: Migrations](https://surrealdb.com/learn/schemas/page-03) [4: Data types](https://surrealdb.com/learn/schemas/page-04) [5: Automation](https://surrealdb.com/learn/schemas/page-05) Migrations [6: SurrealKit and the first table](https://surrealdb.com/learn/schemas/page-06) [7: Activities and seed data](https://surrealdb.com/learn/schemas/page-07) [8: Computed and asserted fields](https://surrealdb.com/learn/schemas/page-08) [9: Graph dependencies](https://surrealdb.com/learn/schemas/page-09) [10: Sync vs rollouts](https://surrealdb.com/learn/schemas/page-10) [11: Milestones](https://surrealdb.com/learn/schemas/page-11) [12: People and indexes](https://surrealdb.com/learn/schemas/page-12) [13: Events and CI](https://surrealdb.com/learn/schemas/page-13) [14: Capstone](https://surrealdb.com/learn/schemas/page-14)

# 13: Events and CI

`DEFINE EVENT` handles the transitions that field clauses cannot reach: rules across two fields on the same record, reactions to a status change, side effects on a different table. This chapter adds a few events to the planning schema.

The second half turns to something SurrealKit has been quietly setting up for you since `init`: tests. Once the catalog does real work (`COMPUTED` roll-ups, `UNIQUE` indexes, `EVENT` hooks), a pull request can break those behaviours without anyone noticing in CI. `surrealkit test` is how you catch that before merge. We will write one small suite after the events are in place.

## Events vs field clauses

| Layer | Examples |
| --- | --- |
| `DEFAULT`, `VALUE`, `ASSERT`, `COMPUTED` | Rules that only need one field, or the record it lives on |
| `DEFINE EVENT` | "When X changes to Y, do Z" across the whole write |

Part [5](https://surrealdb.com/learn/schemas/page-05) already used an order-paid style event. Planning schemas need the same kind of thing.

## Keeping activity dates sane

Add `database/schema/events_activity.surql`:

```surql
DEFINE EVENT activity_end_after_start ON activity
    WHEN $event IN ["CREATE", "UPDATE"]
        AND $after.end < $after.start
    THEN {
        THROW "Activity end must be on or after start";
    };
```

You could try this as an `ASSERT` (`$after.end > $this.start`), and it would mostly work on update, but it gets awkward on create, before `$this` is fully wired up. An `EVENT` sees `$before` and `$after` for both `CREATE` and `UPDATE`, so it does not run into that problem.

If you try it out, you will not see this event's message yet. The `COMPUTED` `duration` field from part [8](https://surrealdb.com/learn/schemas/page-08) is evaluated during the write as well, and `end - start` fails first when `end` is earlier than `start`:

```surql
UPDATE activity:concrete SET end = start - 1d;
```

Output

```surql
'Failed to compute: "2026-12-29T03:37:34.173765Z - 2026-12-30T03:37:34.173765Z", as the operation results in a negative value.'
```

The bad write is rejected either way, but the error says nothing about activities. To let the event give the clearer message, make `duration` handle the case itself:

```surql
DEFINE FIELD duration ON activity COMPUTED IF end >= start { end - start };
```

## Stopping an activity depending on itself (optional)

```surql
DEFINE EVENT no_self_dependency ON depends_on
    WHEN $event = "CREATE" AND $after.in = $after.out
    THEN {
        THROW "An activity cannot depend on itself";
    };
```

This only catches the one-step case. Longer cycles (A depends on B, which depends on A) usually need application logic or a batch job to find. Treat `EVENT` hooks as cheap invariants on the hot path, not a general-purpose validator.

## Logging when a milestone's activity finishes

When an activity's `progress` reaches completion, you might want to notify something downstream. In a teaching schema, an audit record is a reasonable stand-in:

```surql
DEFINE TABLE activity_audit SCHEMAFULL;
DEFINE FIELD activity ON activity_audit TYPE record<activity>;
DEFINE FIELD note ON activity_audit TYPE string;
DEFINE FIELD at ON activity_audit TYPE datetime DEFAULT time::now();

DEFINE EVENT log_activity_complete ON activity
    WHEN $event = "UPDATE"
        AND $before.progress < 1.0
        AND $after.progress >= 1.0
    THEN {
        CREATE activity_audit SET
            activity = $after.id,
            note = "Activity marked complete";
    };
```

`EVENT` bodies run as part of the statement that triggered them: the `activity_audit` record exists as soon as the `UPDATE` returns, and a `THROW` inside an event (as in the two events above) rejects the whole write. `DEFINE EVENT … ASYNC` is the form that runs later, outside the triggering statement, and that is where you need to design for retries and make failures visible.

## Keep events with the schema

`DEFINE EVENT` belongs next to the tables it touches, or in a shared `database/schema/events.surql` if you would rather group them. Either way, SurrealKit applies events on `sync` or `rollout start` like any other definition. Removing one is destructive, so that waits for `rollout complete`, once nothing depends on it any more.

## Why SurrealKit has a test command

Schema in git is only half the story. You also want a machine to prove that the catalog still behaves the way the course (and your app) expect, such as:

- Does `milestone.progress` still average the linked activities?
- Does a bad date still get rejected by the event you just added?
- Did someone remove a `UNIQUE` index by accident?

`surrealkit test` answers those questions against a throwaway database: apply the schema, run suite files under `database/tests/suites/`, fail if a case breaks. It is the CI twin of local `sync` - fine to run often, never a substitute for `rollouts` on shared data.

### A first test suite

Init already left `database/tests/suites/smoke.toml`. Add a second suite beside it at `database/tests/suites/milestone_rollups.toml`:

```toml
name = "milestone_rollups"
tags = ["computed"]

[[cases]]
name = "mean_progress_matches_activities"
kind = "schema_behavior"
actor = "root"
setup_sql = [
  "CREATE project:test SET name = 'Test';",
  "CREATE activity:a SET name = 'A', start = time::now(), end = time::now() + 1d, progress = 0.5;",
  "CREATE activity:b SET name = 'B', start = time::now(), end = time::now() + 2d, progress = 1.0;",
]
action_sql = "CREATE milestone:m SET project = project:test, activities = [activity:a, activity:b], name = 'M1';"
verify_sql = "SELECT VALUE progress FROM ONLY milestone:m;"
expect_success = true
assertions = [
  { path = ".", equals = 0.75 },
]
```

Run it with:

```bash
surrealkit test --user root --pass secret --ns main --db main
```

```text
Test run: PASS (187ms)

Summary
- suites: 2 total, 0 failed
- cases : 2 total, 2 passed, 0 failed
```

SurrealKit loads every `*.toml` under `database/tests/suites/` (including the default smoke suite, which is the second suite in that count). `test` spins up an ephemeral database per suite, applies the schema, runs your seed files, runs the cases, and fails CI the moment an assertion breaks. Because it seeds, expect the schema-apply and seed output to repeat once per suite before the summary; `--no-seed` skips that step if a suite would rather start from an empty database. Good early targets:

- `COMPUTED` roll-ups, so a schema edit cannot quietly change the numbers
- `UNIQUE` indexes, to confirm they are actually there
- `EVENT` definitions, to confirm they apply without error (and later, that they fire when you write the right data)
- Permissions and `DEFINE API` surfaces, if you add either later

See [testing with SurrealKit](https://surrealdb.com/docs/manage/schema-migration/testing) for the rest of the options.

## A CI sketch

```yaml
# .github/workflows/schema.yml (illustrative)
- run: surrealkit test
  env:
    SURREALDB_HOST: http://localhost:8000
    SURREALDB_NAMESPACE: ci
    SURREALDB_NAME: planning
```

Pair `test` with a review of the `rollout plan` on any pull request that touches `database/schema/`. Two more checks catch a schema change that nobody planned a rollout for:

- `surrealkit rollout lint`, with no id, checks every manifest and how they chain, without a database. It fails when `database/schema/` has changes that no rollout plans yet.
- `schema_from = "both"` under `[defaults]` in `database/tests/config.toml` runs every suite twice: once on a database built with `sync`, and once on a database built by replaying every rollout from empty, the way production got its schema. A parity check runs first and lists any definition on which the two disagree.

```toml
# database/tests/config.toml
[defaults]
timeout_ms = 10000
schema_from = "both"
```

## Checkpoint

- `DEFINE EVENT` for date validation, edge-case rules, and completion side effects
- `surrealkit test` to guard against regressions
- Events and indexes versioned right alongside the tables

Next: capstone, aligning with the docs sample, `rollout baseline`, and running schema across environments.

At a glance

**You are on**

Chapter 13 of 14

**Chapters**

14

**Format**

Text, with queries you can run

**Runs in**

SurrealDB Studio, in the browser

**Cost**

Free

**Then take**

[A tour of SurrealDB](https://surrealdb.com/learn/tour)

[Next: 14: Capstone](https://surrealdb.com/learn/schemas/page-14)

## Continue

### [12: People and indexes](https://surrealdb.com/learn/schemas/page-12)

Previous

### [14: Capstone](https://surrealdb.com/learn/schemas/page-14)

Next lesson

THE PLATFORM

## Everything an application and its agents know. Five surfaces, one engine.

Database

Document, graph, vector, time-series and relational in one engine.

![Five data models as dotted tiles: documents, graph, vector, time-series and relational](https://surrealdb.com/assets/static/platform-database.DUdumYDz.avif)

Read more

[Database](https://surrealdb.com/surrealdb)

Agent Memory

What an agent learns, with its source and its time, in the same engine.

![A timeline of remembered facts, each with its source](https://surrealdb.com/assets/static/platform-agent-memory.Cvv8D5Wl.avif)

Read more

[Agent Memory](https://surrealdb.com/agent-memory)

Cloud

Managed clusters in the regions you choose, scaled on demand.

![Clusters in three regions on a world map, each running or scaling](https://surrealdb.com/assets/static/platform-cloud.--QZnaVi.avif)

Read more

[Cloud](https://surrealdb.com/cloud)

Studio

Query, explore and design the schema from the browser.

![A SurrealQL query in Studio and the schema graph under it](https://surrealdb.com/assets/static/platform-studio.7ykBFNLq.avif)

Read more

[Studio](https://surrealdb.com/studio)

MCP

Every model that speaks MCP reaches the database and the memory directly.

![Three models connected through MCP to the database and Agent Memory](https://surrealdb.com/assets/static/platform-mcp.D_oH0_wm.avif)

Read more

[MCP](https://surrealdb.com/mcp)

IN PRODUCTION

## Trusted at scale. Samsung, Nvidia, Verizon, Tencent, and Walmart run on SurrealDB.

14,000+

Developers building on SurrealDB Cloud

4M+

Developers building on SurrealDB worldwide

FROM THE TEAMS

> SurrealDB gives us a foundation where we can unify semantic search, knowledge graphs, and AI-driven decision making without stitching together multiple systems. Collapsing responsibility into SurrealDB has become our default engineering posture.

*Justin Foley*

VP of Engineering, Later

```json
{"@context":"https://schema.org","@type":"Course","name":"Schema internals and migrations","description":"Learn how SurrealDB stores schema metadata, how DEFINE statements shape your database, and how to migrate production data safely.","url":"https://surrealdb.com/learn/schemas","inLanguage":"en","isAccessibleForFree":true,"provider":{"@type":"Organization","name":"SurrealDB","url":"https://surrealdb.com"},"hasPart":[{"@type":"LearningResource","name":"Schema internals and migrations","url":"https://surrealdb.com/learn/schemas"},{"@type":"LearningResource","name":"1: Schemaless vs. schemafull","url":"https://surrealdb.com/learn/schemas/page-01"},{"@type":"LearningResource","name":"2: Schema internals","url":"https://surrealdb.com/learn/schemas/page-02"},{"@type":"LearningResource","name":"3: Migrations","url":"https://surrealdb.com/learn/schemas/page-03"},{"@type":"LearningResource","name":"4: Data types","url":"https://surrealdb.com/learn/schemas/page-04"},{"@type":"LearningResource","name":"5: Automation","url":"https://surrealdb.com/learn/schemas/page-05"},{"@type":"LearningResource","name":"6: SurrealKit and the first table","url":"https://surrealdb.com/learn/schemas/page-06"},{"@type":"LearningResource","name":"7: Activities and seed data","url":"https://surrealdb.com/learn/schemas/page-07"},{"@type":"LearningResource","name":"8: Computed and asserted fields","url":"https://surrealdb.com/learn/schemas/page-08"},{"@type":"LearningResource","name":"9: Graph dependencies","url":"https://surrealdb.com/learn/schemas/page-09"},{"@type":"LearningResource","name":"10: Sync vs rollouts","url":"https://surrealdb.com/learn/schemas/page-10"},{"@type":"LearningResource","name":"11: Milestones","url":"https://surrealdb.com/learn/schemas/page-11"},{"@type":"LearningResource","name":"12: People and indexes","url":"https://surrealdb.com/learn/schemas/page-12"},{"@type":"LearningResource","name":"13: Events and CI","url":"https://surrealdb.com/learn/schemas/page-13"},{"@type":"LearningResource","name":"14: Capstone","url":"https://surrealdb.com/learn/schemas/page-14"}]}
```

```json
{"@context":"https://schema.org","@type":"LearningResource","name":"13: Events and CI","description":"DEFINE EVENT rules for the planning schema and surrealkit test for automated checks.","url":"https://surrealdb.com/learn/schemas/page-13","learningResourceType":"lesson","isPartOf":{"@type":"Course","name":"Schema internals and migrations","url":"https://surrealdb.com/learn/schemas"},"position":16}
```

```json
{"@context":"https://schema.org","@type":"Organization","@id":"https://surrealdb.com/#organization","name":"SurrealDB","url":"https://surrealdb.com","logo":"https://surrealdb.com/assets/static/logo.BG7_TG2b.svg","description":"SurrealDB is the context and memory layer for AI agents. A multi-model database for documents, graphs, vectors, and time-series.","foundingDate":"2022","legalName":"SurrealDB Ltd","identifier":{"@type":"PropertyValue","propertyID":"GB-COH","value":"13615201"},"address":{"@type":"PostalAddress","streetAddress":"3rd Floor, 1 Ashley Road","addressLocality":"Altrincham","addressRegion":"Cheshire","postalCode":"WA14 2DT","addressCountry":"GB"},"contactPoint":[{"@type":"ContactPoint","contactType":"customer support","email":"support@surrealdb.com","url":"https://surrealdb.com/contact","availableLanguage":"English"},{"@type":"ContactPoint","contactType":"sales","email":"info@surrealdb.com","url":"https://surrealdb.com/contact","availableLanguage":"English"},{"@type":"ContactPoint","contactType":"security","email":"security@surrealdb.com","url":"https://surrealdb.com/.well-known/security.txt","availableLanguage":"English"},{"@type":"ContactPoint","contactType":"legal","email":"legal@surrealdb.com","url":"https://surrealdb.com/legal","availableLanguage":"English"}],"hasCertification":[{"@type":"Certification","name":"SOC 2 Type 2"},{"@type":"Certification","name":"GDPR"},{"@type":"Certification","name":"Cyber Essentials Plus"},{"@type":"Certification","name":"ISO 27001"}],"owns":[{"@type":"SoftwareApplication","name":"SurrealDB","url":"https://surrealdb.com/surrealdb"},{"@type":"SoftwareApplication","name":"Agent Memory","url":"https://surrealdb.com/agent-memory"}],"knowsAbout":["multi-model databases","document databases","graph databases","vector search","time-series databases","SurrealQL","Agent Memory","real-time databases","embedded databases","context layer","graph ontology","distributed database","knowledge graphs","distributed transaction protocols","highly-scalable databases"],"sameAs":["https://www.wikidata.org/wiki/Q124316308","https://github.com/surrealdb/surrealdb","https://twitter.com/surrealdb","https://www.youtube.com/@surrealdb","https://www.linkedin.com/company/surrealdb","https://discord.gg/surrealdb","https://www.reddit.com/r/surrealdb","https://www.instagram.com/surrealdb","https://medium.com/surrealdb","https://dev.to/surrealdb"]}
```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://surrealdb.com"},{"@type":"ListItem","position":2,"name":"Learn","item":"https://surrealdb.com/learn"},{"@type":"ListItem","position":3,"name":"Page 13","item":"https://surrealdb.com/learn/schemas/page-13"}]}
```
