---
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)

[Back to Courses](https://surrealdb.com/learn)

Course chapters

[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.

## 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 asynchronously relative to the statement that triggered them, so design for idempotency, and make failures visible, before you rely on this in production.

## 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/`.

## 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.

Previous

12: People and indexes

[Previous](https://surrealdb.com/learn/schemas/page-12)

Next lesson

14: Capstone

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

```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","name":"SurrealDB","url":"https://surrealdb.com","logo":"https://surrealdb.com/assets/static/logo.BG7_TG2b.svg","description":"SurrealDB is the unified data layer for AI. 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"}]}
```
