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

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

# 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
Updated ./database/snapshots/catalog_snapshot.json
```

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"
files = ["database/schema/milestone.surql"]
```

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 any schema file after planning, the hash will no longer match and `start` will refuse. Re-plan in that case. Also make sure each field appears only once in a file: two `DEFINE FIELD progress` lines, for example, share one metadata key and can break start.

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 and the new `database/rollouts/*.toml` file (plus updated snapshots if SurrealKit refreshed them). 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
```

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

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`

Previous

10: Sync vs rollouts

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

Next lesson

12: People and indexes

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

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