---
title: "11: Milestones | SurrealDB University"
description: "Milestone roll-ups previewed with sync --dry-run, then applied once with a SurrealKit rollout."
url: https://surrealdb.com/learn/schemas/page-11
---

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

# 11: Milestones

Milestones group activities and expose roll-up `progress`. You already created `database/schema/milestone.surql` in part [10](https://surrealdb.com/learn/schemas/page-10) and left it sitting there. Now we will look at what the file does, preview the change with `surrealkit sync --dry-run`, then apply it once with a `rollout`, the same path you would use on a shared database.

On a normal day you would just `sync` this locally. We are skipping that apply on purpose so you can practise plan → start → complete without putting the same definitions in twice.

## The parked `milestone` file

Here is the file again for reference:

```surql
DEFINE TABLE milestone SCHEMAFULL;
DEFINE FIELD project ON milestone TYPE record<project>;
DEFINE FIELD activities ON milestone TYPE array<record<activity>>;
DEFINE FIELD name ON milestone TYPE string;
DEFINE FIELD last_updated ON milestone VALUE time::now();
DEFINE FIELD progress ON milestone COMPUTED math::mean(activities.progress);
DEFINE FIELD is_complete ON milestone COMPUTED activities.all(|$a| $a.progress > 0.95);
```

| Field | Mechanism |
| --- | --- |
| `last_updated` | `VALUE` `time::now()` on every write |
| `progress` | `COMPUTED` mean over linked activities |
| `is_complete` | `COMPUTED` threshold across the group |

If the file is missing, create it with the statements above before continuing. Still do not `sync` it: dry-run first, then rollout.

## Preview with `sync --dry-run`

Do not run a normal `surrealkit sync` yet. Ask what sync *would* do:

```bash
surrealkit sync --dry-run --user root --pass secret --ns main --db main
```

You should see `milestone.surql` (and maybe other files whose hashes have changed) listed as pending applies. The live catalog stays the same. If something looks wrong, fix the file and dry-run again.

One item to keep in mind is that dry-run output is fairly coarse. Applies are reported per file, not per field. Prunes only show up when SurrealKit has stale managed entities to remove. For a detailed expand/contract list, open the rollout manifest after you plan.

Important

After a clean dry-run, leave the catalog alone until `rollout start`. A real `sync` (or `sync --watch`) would apply `milestone` immediately, and the later rollout would have little or nothing left to expand.

## Plan and apply with a rollout

### 1. Plan

From the SurrealKit project root (the folder that contains `database/`):

```bash
surrealkit rollout plan --name add_milestones
```

```text
Generated rollout manifest ./database/rollouts/20260302153045__add_milestones.toml
Froze 1 schema file(s) into ./database/rollouts/20260302153045__add_milestones
Updated ./database/snapshots/catalog_snapshot.json
Commit the manifest, its directory and the snapshots together; reverting means reverting all three.
```

The `manifest id` is that filename without `.toml`:

```text
database/rollouts/20260302153045__add_milestones.toml
→ manifest id: 20260302153045__add_milestones
```

Your timestamp will differ. Planning compares `database/schema/` to snapshots under `database/snapshots/` (part [14](https://surrealdb.com/learn/schemas/page-14) covers `rollout baseline` when you adopt a brownfield database).

Open the manifest and the diff should be exactly one file, because part [10](https://surrealdb.com/learn/schemas/page-10)'s probe left the snapshots matching everything else:

```toml
[[steps]]
id = "apply_expand_schema"
phase = "start"
kind = "apply_files"

[[steps.files]]
path = "schema/milestone.surql"
hash = "63c13afa06104270ac0a377ee7e9865025c0bb531692e60720ec2a0c194c8a94"
```

If yours lists all five schema files, you skipped the probe in part 10; that is harmless here, since re-applying an unchanged definition is a no-op, but the rest of this chapter is easier to follow with a one-file plan.

If you edit a schema file after planning, `start` still applies the copy frozen into `database/rollouts/20260302153045__add_milestones/`, so the edit is not part of this rollout. `surrealkit rollout lint` without an id reports it as a change that no rollout plans yet, so here you should plan another rollout to include it.

Optional check before start:

```bash
surrealkit rollout lint 20260302153045__add_milestones
```

```text
Rollout 20260302153045__add_milestones is valid (checksum e6d2adfc13c4c86db8633974fbc7c378282d5f9a3c767240470ac1c135c84545).
```

### 2. Commit the plan (when this is real work)

On a team project, commit the schema diff, the new `database/rollouts/*.toml` file with its directory along with the updated snapshots. Reviewers should see the intended apply, not only the `.surql` changes. For this course exercise you can keep going without a git commit.

### 3. Start (this is the apply)

Use your manifest id from step 1:

```bash
surrealkit rollout start 20260302153045__add_milestones --user root --pass secret --ns main --db main
```

```text
Rollout 20260302153045__add_milestones is ready to complete.
```

`start` applies the non-destructive steps: new tables, new fields, new indexes (part [12](https://surrealdb.com/learn/schemas/page-12)). Check that `milestone` shows up in `INFO FOR DB`, and peek at the fields with `INFO FOR TABLE milestone`.

`surrealkit rollout status` now shows the rollout sitting between its two phases:

```text
__rollout:20260302153045__add_milestones [ready_to_complete] add_milestones
  started_at: 2026-03-02T15:31:12.694795Z
  - apply_expand_schema [start:apply_files] completed
```

Below the rollout records, `status` also prints where the database sits in the chain of rollouts and which rollouts it has not run yet.

In production you would deploy the app that uses milestones while the old and new code can still coexist, then run `complete`.

```bash
surrealkit rollout complete 20260302153045__add_milestones --user root --pass secret --ns main --db main
```

```text
Completed rollout 20260302153045__add_milestones.
```

`complete` is where destructive steps live: `REMOVE FIELD`, `REMOVE TABLE`, dropped indexes, and so on. Adding `milestone` only adds things, so this manifest has no `complete` step at all and the command simply moves the status to `completed`. Running it anyway is a habit worth keeping, because the moment a change does remove something, that becomes the phase in which it lands.

If something goes wrong after `start`:

```bash
surrealkit rollout rollback 20260302153045__add_milestones --user root --pass secret --ns main --db main
```

After a rollback, `rollout status` lists the rollout as rolled back, and `rollout up` stops at it rather than running it again by itself. There are two ways on:

- If the rollout itself was fine and the problem was elsewhere, run it again with `surrealkit rollout start 20260302153045__add_milestones`.
- If the change needs to be different, drop the rollout with `surrealkit rollout discard 20260302153045__add_milestones`. This deletes the manifest and its directory and puts `database/snapshots/` back to where they were before the plan. Then fix the schema file and plan again.

Only discard a rollout that no database has completed. A database that has already run it has nowhere in the chain to stand once the manifest is gone.

Once a rollout is `completed`, that door is shut:

```text
Error: rollout '20260302153045__add_milestones' is already completed
```

Note

Dry-run answered “what would sync do?” Rollout `start` answered “apply the expand.” On a shared host, CI holds the credentials for `start` / `complete`. Your laptop `.env` should keep pointing at disposable databases for everyday `sync`.

## Seed data

Run these statements now in SurrealDB Studio or `surreal sql` so your current database has the milestones. Also append them to `database/seed/demo_project.surql` so a later fresh database gets the same records on first `seed`. Do not re-run `surrealkit seed` here: appending to the file changes its hash, so seed would replay the whole thing, starting with `CREATE project:one` and the earlier activities, and fail with “already exists” (part [7](https://surrealdb.com/learn/schemas/page-07)).

```surql
CREATE milestone:start SET
    project = project:one,
    activities = [activity:kickoff],
    name = "Project start";

CREATE milestone:construction SET
    project = project:one,
    activities = [activity:concrete],
    name = "Initial construction";
```

Then inspect the roll-ups:

```surql
SELECT name, progress, is_complete FROM milestone;
```

## Expand-contract without renaming

Part [3](https://surrealdb.com/learn/schemas/page-03)’s gradual migration pattern (union types, backfill, tighten) is about data. Rollouts are about catalog changes:

| Data (SurrealQL scripts) | Catalog (SurrealKit) |
| --- | --- |
| `UPDATE` to backfill | `rollout start` adds `milestone` |
| `DEFINE EVENT` normaliser | Deploy app |
| `ALTER FIELD` tighten | `rollout complete` removes deprecated defs |

SurrealDB has no field-rename statement, and SurrealKit will not ask “did you mean rename?” when one `DEFINE FIELD` disappears and another appears. If you `REMOVE FIELD description` and `DEFINE FIELD class` in one go, existing values under `description` stay on the records as orphaned data (or vanish from a SCHEMAFULL view) unless you copy them first.

Treat a rename the same way as promoting a string field to `record<table>`: add the new field (expand), `UPDATE` to copy or map values (data script / seed), then remove the old field on `rollout complete` (contract). Do not fold “new name + drop old name” into a single `sync` on a shared database.

## Checkpoint

- `milestone` with `COMPUTED` roll-ups
- Previewed with `sync --dry-run`, applied once with `rollout start` (then `complete`)
- Seed records by hand, and the same statements appended to `demo_project.surql`
- A real `manifest id` from `database/rollouts/` for `start` / `complete` / `rollback`

At a glance

**You are on**

Chapter 11 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: 12: People and indexes](https://surrealdb.com/learn/schemas/page-12)

## Continue

### [10: Sync vs rollouts](https://surrealdb.com/learn/schemas/page-10)

Previous

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

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":"11: Milestones","description":"Milestone roll-ups previewed with sync --dry-run, then applied once with a SurrealKit rollout.","url":"https://surrealdb.com/learn/schemas/page-11","learningResourceType":"lesson","isPartOf":{"@type":"Course","name":"Schema internals and migrations","url":"https://surrealdb.com/learn/schemas"},"position":14}
```

```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 11","item":"https://surrealdb.com/learn/schemas/page-11"}]}
```
