Skip to content

[NS 6] Migrations: handle mounted submodule tables, views, and indexes#5174

Open
aasoni wants to merge 63 commits into
alessandro/handle-module-mounts-in-clifrom
alessandro/handle-module-mounts-in-migrations
Open

[NS 6] Migrations: handle mounted submodule tables, views, and indexes#5174
aasoni wants to merge 63 commits into
alessandro/handle-module-mounts-in-clifrom
alessandro/handle-module-mounts-in-migrations

Conversation

@aasoni

@aasoni aasoni commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Description of Changes

Handle module migrations for modules that have mounted sub-modules. This required major surgery on the the migration step types which used "Identifier" which doesn't allow for dots or slashes.
It also required significant changes in the code that gathers and compares changes between the old and new module to correctly compare namespaced types with the corresponding mounted types.

API and ABI breaking changes

No

Expected complexity level and risk

4 - Significant change to migration code and logic. This shouldn't effect any logic for migrations that affect modules that don't have mounts, but the change is extensive enough that I think 4 is warrented.

Testing

Beyond the rust tests in the codebase that test various auto-migration cases for mounted modules I also did the following tests on sample module that had a mounted sub-module library:

  • Module migrates without issue from having a submodule to not having a submodule
  • Module migrates without issue from not having a submodule to having a submodule
  • Module migrates without issue when having a submodule and root module change occurs (change reducer signature, add table, add column with default, change reducer function body, change index)
  • Module migrates without issue when having a submodule and a submodule change occurs (change reducer signature, add table, add column with default, change reducer function body, change index)

@aasoni aasoni changed the title Migrations: handle mounted submodule tables, views, and indexes [NS 6] Migrations: handle mounted submodule tables, views, and indexes Jun 2, 2026
@aasoni aasoni force-pushed the alessandro/handle-module-mounts-in-cli branch from fe0654d to a284b57 Compare June 3, 2026 08:03
@aasoni aasoni force-pushed the alessandro/handle-module-mounts-in-migrations branch from 2dfc6d4 to be36425 Compare June 3, 2026 08:03
@aasoni aasoni force-pushed the alessandro/handle-module-mounts-in-cli branch from a284b57 to e54ed46 Compare June 3, 2026 12:22
@aasoni aasoni force-pushed the alessandro/handle-module-mounts-in-migrations branch from be36425 to 4fb542f Compare June 3, 2026 12:22
@aasoni aasoni requested a review from coolreader18 June 3, 2026 20:00
@aasoni aasoni force-pushed the alessandro/handle-module-mounts-in-migrations branch from 4fb542f to 4be78f3 Compare June 4, 2026 09:05

@coolreader18 coolreader18 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, though it'd probably be good to have someone more familiar with automigration also take a look.

Comment thread crates/schema/src/identifier.rs Outdated
pub fn new(s: impl Into<Box<str>>) -> Result<Self, IdentifierError> {
let s = s.into();
for segment in s.split(['.', '/']) {
Identifier::new(RawIdentifier::new(segment))?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have the validation logic in Identifier::new pulled out into a separate function that returns Result<(), IdentifierError>, so we're not doing a bunch of unnecessary allocation here.

clockwork-labs-bot and others added 8 commits June 9, 2026 17:16
## Summary
- Remove the clap-level requirement that forced `spacetime publish
--delete-data` to include a positional database name.
- Keep database-name validation in the merged command config, so
`spacetime.json` can still provide `database`.
- Add regression coverage for `publish -c=always` using a config-sourced
database.

Closes #5253.

## Tests
-
`PATH=/Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin:$PATH
cargo fmt --all --check`
-
`PATH=/Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin:$PATH
cargo test -p spacetimedb-cli subcommands::publish::tests`

Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
…5131)

# Description of Changes

Prior to this commit, the metric `wasm_memory_bytes` had several
problems:

1. Despite its name, it was used for both Wasmtime and V8 modules. For
V8 modules, it was the same value as `v8_used_heap_size_bytes`.
2. It stored only the value for a single instance at any given time, so
it under-reported a database's memory usage.
3. The same row (set of label values) was written concurrently by all
instances of a particular database, with each one clobbering the
previously written value.

In this commit, we change the metric so that:

1. It is recorded only for Wasmtime instances, not V8 instances. For V8
instances, instead directly check `v8_used_heap_size_bytes`, or one of
the other V8 heap metrics. This change involved moving the recording of
this metric from `module_host_actor.rs` to
`wasmtime/wasm_instance_env.rs`
2. Similar to the V8 heap metrics, all the instances cooperatively share
the metric entry, updating it by incrementing and decrementing rather
than `set`ting.

Note that this metric is used for billing, and so we will need to update
our billing code (elsewhere) to account for the change. In particular,
our billing code should now charge for the sum of `wasm_memory_bytes`
and `v8_used_heap_size_bytes`. We also should expect with this change
for each database's recorded usage to increase, as we are now accurately
recording the usage for all instances, not just one.

# API and ABI breaking changes

Billing metric semantics changed.

# Expected complexity level and risk

3: billing metric semantics changed.

# Testing

I do not know how to test metrics.
Makes the `call` command rewrite reducer arguments typed at `Identity`,
but given as just a (hex) string, into a JSON 1-tuple.

Proper deserialization on the server is more complicated to change, so
may be addressed separately.

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

Added a smoketest.
# Description of Changes

Reviving a previous patch I wrote during our (internal) TPCC
experimentation. This has become important because, in addition to its
performance implications, it makes row insertion locations deterministic
regardless of datastore restarts, which previously they were not.

Previously, restarting the datastore would re-order the `non_full_pages`
list (i.e. sort it by increasing `PageIndex`, where normally it was not
sorted), meaning that which page a new row would be inserted into
depended on when the datastore was last restarted.

With this patch, that is not the case: the `non_full_pages` are always
kept in a deterministic order, so which page a new row goes into is also
deterministic.

Original commit message follows:

And sort them by number of available var-len granules. This prevents an
accidentally quadratic behavior where, for a table where the average row
contains many var-len granules, after inserting a large number of rows,
there would be a large number of pages in `non_full_pages` each of which
had enough space for at least one fixed-len row part, but insufficient
space for an actual row in practice due to insufficient var-len
granules. Each insertion would then do a linear scan over
`non_full_pages` before either inserting into the last page or
allocating a new page which went to the end.

Now, non-full pages are stored in a `BTreeSet` sorted by the number of
free var-len granules, and the search for a useable page is done with a
`BTreeSet::range` iterator for only the pages with enough granules. I
think there may still be an off-by-one-ish bug here, where a page may
have enough bytes in the gap that it could either store the fixed-len
part or the var-len granules, but not both, but this fix hopefully will
suffice for now.

# API and ABI breaking changes

N/a

# Expected complexity level and risk

2? Table code is a bit fiddly, and this path is performance-sensitive
when inserting rows.

# Testing

- [x] Passes table crate tests.
- [x] Was included in our internal TPCC experimentation, where it
significantly improved performance (due to that benchmark exercising the
accidentally-quadradic behavior this patch is designed to protect).
- [x] Joshua ran the keynote-2 benchmarks with this patch and did not
observe a decrease in throughput.
# Description of Changes

Add several new tests of concurrency behavior re: procedures.

The new tests are in the SDK tests, 'cause I thought the easiest way to
observe this behavior was by connecting a client to a database and
calling some functions in it. This is yet another mild misuse of the SDK
test suite, as the behavior in question is not in the SDK, it's in the
host. The tests also have a new module/client pair added, as we don't
(yet?) expose `procedure_sleep_until` to any module languages other than
Rust, so we can't implement the same test in any other languages.

### `procedure_reducer_interleaving`

Verifies that a procedure and a reducer can run concurrently, with the
procedure cooperatively yielding using `ctx.sleep_until`. Uses two
separate connections due to #4954 .

### `procedure_reducer_same_client_interleaved`

Same as previous, but with only a single connection. Now that #4954 is
closed, this has the same semantics as previous.

### `procedure_concurrent_with_scheduled_reducer`

Verifies that a non-scheduled procedure can schedule a reducer and then
sleep, and the reducer will execute before the procedure wakes back up.

### `scheduled_procedure_scheduled_reducer_not_interleaved`

Schedules a procedure and a reducer, which you might expect to execute
concurrently, but don't in a way similar to
`procedure_reducer_same_client_not_interleaved`.

This is the behavior that kicked off this whole thing. The scheduler
subsystem behaves like a single client, in the sense that it waits for a
single function to terminate before scheduling the next function.

# API and ABI breaking changes

N/a

# Expected complexity level and risk

1 - tests

# Testing

- [x] Ran the tests!

I didn't bother intentionally breaking the host to verify that the tests
would fail. It feels pretty apparent to me based on just the test code
that we won't see false negatives.
# Description of Changes

Adds primary keys to procedural views in C#. See for #5111 for the
equivalent feature in rust and C# as well as a more detailed
description.

# API and ABI breaking changes

None

# Expected complexity level and risk

3

# Testing

- [x] Equivalent tests as were added in #5111 for rust and typescript
# Description of Changes

Bump version to `2.5.0` for new release

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1

# Testing

None
@aasoni aasoni force-pushed the alessandro/handle-module-mounts-in-cli branch from e54ed46 to 214bc9a Compare June 10, 2026 14:00
@aasoni aasoni force-pushed the alessandro/handle-module-mounts-in-migrations branch from 4be78f3 to 7d16c66 Compare June 10, 2026 14:01
bradleyshep and others added 9 commits June 10, 2026 16:37
# Description of Changes

AI app generation benchmark comparing SpacetimeDB vs PostgreSQL (Express
+ Socket.io + Drizzle ORM). Same AI model (Claude Sonnet 4.6), same
prompts, same chat app, two backends. Upgraded through 12 feature
levels, manually graded at each level, bugs fixed, all costs measured
via OpenTelemetry.

Results viewable at:
https://spacetimedb.com/llms-benchmark-sequential-upgrade

## Benchmark harness (`tools/llm-sequential-upgrade/`)

- `run.sh`: orchestrates headless Claude Code sessions for code
generation, sequential upgrades, and bug fixes. Tracks all API costs via
OTel. Supports `--upgrade`, `--fix`, `--composed-prompt`,
`--resume-session` modes.
- `grade.sh` / `grade-agents.sh`: grading harnesses for manual testing
of generated apps.
- `docker-compose.otel.yaml`: OTel collector + PostgreSQL services.
- `generate-report.mjs` / `parse-telemetry.mjs`: aggregate per-session
telemetry into cost reports.
- Backend guidelines in `backends/`: SpacetimeDB SDK reference, config
templates, server setup docs, PostgreSQL setup with Drizzle/Socket.io
guidance.
**After #4740 merges,
we will likely want to update this so that it reads backend and SDK
guidance from SKILLS**

## Two complete benchmark runs

**Run 1 (20260403):** Original methodology.
**Run 2 (20260406):** Refined methodology with domain bias removed from
SpacetimeDB SDK docs and PostgreSQL instructions made
feature-spec-neutral.
**Note: no meaningful changes in results were observed with these
changes. Domain familiarity biases were very small and almost certainly
not the cause of STDB's major gains over PG stack.**

Each run contains full L1-L12 app source for both backends, level
snapshots preserving state before each upgrade, and per-session OTel
cost summaries.

## 12 feature levels

| Level | Feature |
|---|---|
| L1 | Basic Chat + Typing + Read Receipts + Unread Counts |
| L2 | Scheduled Messages |
| L3 | Ephemeral Messages |
| L4 | Message Reactions |
| L5 | Message Editing with History |
| L6 | Real-Time Permissions (kick, ban, promote) |
| L7 | Rich User Presence |
| L8 | Message Threading |
| L9 | Private Rooms + Direct Messages |
| L10 | Room Activity Indicators |
| L11 | Draft Sync |
| L12 | Anonymous to Registered Migration |

## Results

| | Run 1 (20260403) | Run 2 (20260406) |
|---|---|---|
| **SpacetimeDB total cost** | $13.33 | $12.62 |
| **PostgreSQL total cost** | $17.80 | $19.68 |
| **SpacetimeDB bugs** | 5 | 2 |
| **PostgreSQL bugs** | 19 | 8 |
| **SpacetimeDB fix sessions** | 4 | 1 |
| **PostgreSQL fix sessions** | 17 | 10 |

Both runs agree: SpacetimeDB apps are cheaper to build, have fewer bugs,
and require fewer fix iterations. The refined methodology (Run 2)
widened the cost gap and **confirmed the advantage is structural, not an
artifact of domain-biased SDK docs.**

## Performance benchmark (`perf-benchmark/`)

Stress throughput tool that fires concurrent writers at peak saturation
against the AI-generated `send_message` handlers.

| Tier | SpacetimeDB (avg) | PostgreSQL (avg) | Ratio |
|---|---|---|---|
| AI-generated (as-shipped) | 5,267 msgs/sec | 694 msgs/sec | 7.6x |
| PG rate limit removed | 5,267 msgs/sec | 1,070 msgs/sec | 4.9x |
| Optimized (same features kept) | 25,278 msgs/sec | 1,139 msgs/sec |
22x |

The gap widens with optimization because SpacetimeDB's bottleneck is
fixable code patterns in the reducer while PostgreSQL's bottleneck is
architectural (sequential network round-trips to an external database).

Optimized reference code with all features preserved is in
`perf-benchmark/results/optimized-reference/`.

## Data handling

Per-session cost summaries (`cost-summary.json`, `COST_REPORT.md`,
`metadata.json`) are committed. Raw OTel telemetry
(`raw-telemetry.jsonl`) containing PII is excluded via `.gitignore` and
stored privately.

# API and ABI breaking changes

None. All changes are in `tools/llm-sequential-upgrade/`. No production
code, library, or SDK changes.

# Expected complexity level and risk

**1 - Trivial.** Self-contained benchmarking tooling and data. No
interaction with production code.

# Testing

- [x] L1-L12 upgrades completed on all 4 apps (2 backends x 2 runs) with
OTel cost capture
- [x] All levels manually graded after each upgrade; bugs filed and
fixed via the harness
- [x] Methodology refinement between runs validated (domain bias
removal, feature-neutral instructions)
- [x] Stress benchmarks run across both runs x 3 tiers (as-shipped,
rate-limit-removed, optimized)
- [x] Optimized benchmarks verified to preserve all original features
- [x] Sensitive data (PII in raw telemetry) removed from repo and
gitignored
- [ ] Reviewer: spot-check that METRICS_DATA.json / METRICS_REPORT.json
numbers match the telemetry cost-summary.json files

---------

Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
# Description of Changes

(Moving this to a tools repo)

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

None

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
# Description of Changes

It turns out that different templates have different behavior for
whether they get a major+minor version constraint, a major+minor+patch
version constraint, or, surprisingly, just a major version constraint.
See #5229 for a bit
more detail.

This PR brings them all in line to major+minor.

This fixes a bug where we could release a newer version of the
server+CLI, but not the crates, and that would cause the CLI to
initialize some templates to expect a version number that did not exist.

**I am not 100% sure that this doesn't have surprise consequences**,
since this is a weird situation in the first place.

# API and ABI breaking changes

None

# Expected complexity level and risk

2

# Testing
spot tests, but more importantly some template smoketests have been
added to check that the version constraints are now `major.minor` on
some representative templates.

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
# Description of Changes

This commit adds special handling in automigrations to accept a broad
set of schema- and layout-altering automigrations on event tables,
including changes that we'd reject for non-event tables like removing or
reordering columns, or layout-incompatibly changing the types of
existing columns.

This is due to a change we want to make to the ControlDB schema, where
we have a product type which is used both as a table type, and as the
column type of an event table, and we want to add an element to that
product type.

I've added a simple smoketest of the new behavior,
`automigrate_reschema_event_table_arbitrarily`. Note that I have not
tested commitlog replay of a database which has undergone one of these
migrations, which has been a common place that bugs have appeared in
similar changes in the past.

I have done a pretty lazy job with the migration plan formatter for the
new step, just printing the name of the event table which is changing,
not any information about the specific changes or the columns. This is
in an effort to save time, as we'd like to release the ControlDB change
blocked by this.

# API and ABI breaking changes

N/a

# Expected complexity level and risk

3 at least: new categories of automigrations have a high risk to
introduce commitlog replay bugs, and it's also possible I have
misunderstood or mis-remembered some of the safety invariants of the
`table` crate.

# Testing

- [x] New (minimal) smoketest.
- [x] Manually tested replay:
- Copied event table from the before version new smoketest into
module-test.
  - Published module-test.
- Replaced event table with after version, published, observed and
approved client-breaking automigration.
  - Restarted SpacetimeDB.
- Called the procedure `return_value` in the migrated module to force
commitlog replay.
- [ ] Test the ControlDB change in staging, incl. restarting the control
node.

---------

Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
# Description of Changes

<!-- Please describe your change, mention any related tickets, and so on
here. -->

The objective here is to get rid of the arm runner that we have deployed
which is very much underutilized and sometimes during the release is the
bottleneck because it can only run a small amount of jobs at any given
time. Instead, we will cross compile to ARM on our existing x86 github
runner fleet.

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

None - CI only change.

# Expected complexity level and risk

1 - CI only change

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

https://github.com/clockworklabs/SpacetimeDB/actions/runs/26833018052
# Description of Changes

Fix a broken link

# API and ABI breaking changes

None

# Expected complexity level and risk

Between 0.001 and 0.01

# Testing

- [x] Rendered locally
# Description of Changes

This fixes a scheduler crash where a panic from a scheduled JS
reducer/procedure could unwind out of `SchedulerActor::handle_queued`.

The fix keeps the existing panic semantics through `ModuleHost`, so
`defer_on_unwind` still runs and poisons the failed module host, but the
scheduler now catches the unwind after that boundary, logs a warning,
and returns without rescheduling that queue item.

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1

# Testing

...
## Summary

- document schedule row lifecycle differences for scheduled reducers,
scheduled procedures, and interval schedules
- clarify that scheduled functions should read row data from the
argument they receive
- document the scheduler's in-memory queue behavior when a schedule row
is deleted before its queued entry fires

Fixes #5252

## Testing

- `pnpm build` from `docs/`

Note: docs build completed successfully. It emitted the existing
`docusaurus-plugin-llms-txt` warning for `/docs/ask-ai/ask-ai` being
skipped during HTML-to-Markdown conversion.

---------

Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
joshua-spacetime and others added 22 commits June 15, 2026 21:46
# Description of Changes

Makes runtime parameters explicit in query plans (prerequisite for
parameterized views).

As part of this change, `sender` is no longer baked directly into query
plans as a literal value. Instead it is represented as a parameter in
the query plan. Values are supplied at runtime via a variable
environment called `ExecutionParams`.

Note, parameterized plans are still not shared across subscriptions yet.
That will be done in a follow up.

This is mostly a mechanical change. The majority of the diff is just
threading runtime params/variables through various call sites.

# API and ABI breaking changes

N/A

# Expected complexity level and risk

...

# Testing

Existing coverage
# Description of Changes

All of rust, C#, and typescript are included.

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1

# Testing

N/A - docs only change
# Description of Changes

* Bump version to 2.6.0

# API and ABI breaking changes

None

# Expected complexity level and risk

* 1 - this is just a version bump

# Testing

- [X] Version number is correct (`2.6.0`)
- [X] BSL license file has been updated with the new date and version
number
# Description of Changes

Adding a stub version of this workflow so we can test it with
`workflow_dispatch`

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

# Expected complexity level and risk

1

# Testing

None, can't test until it's merged

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
# Description of Changes

Review #5287 first.

This patch updates view backing table schemas to have a single private
column `arg_hash`. Previously sender-scoped views had a `sender` column
for the calling identity, however now that's been replaced with a single
unified `arg_hash` column that encodes the calling identity within it.

When we add parameterized views, the view args will also be encoded in
this hash and stored in this column.

This column exists for both anonymous and sender scoped views meaning
that the backing tables for all views now have the same number of
private columns - one.

This hash is now used as a runtime variable that the query engine uses
to evaluate view table scans.

In order to keep the diff small, this patch does update view read sets
with this new hash value. That change has a larger blast radius and will
be done in the next set of changes.

# API and ABI breaking changes

N/A

# Expected complexity level and risk

2

# Testing

Existing coverage.
# Description of Changes

Fixes an OOM kill in the proptest `bsatn_invalid_wont_decode`.

`bsatn_invalid_wont_decode` generates arbitrary invalid bytes, proves
validation fails, then still calls full AlgebraicValue::decode. For
generated array-like types, decode reads a u32 length prefix, and the
generic array visitor then reserves that capacity. But because they're
random bytes, this could cause a huge initial allocation which could OOM
kill the test process.

Now the visitor reserves a smaller initial capacity instead of assuming
the binary input data is well formed.

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1

# Testing

This should fix the flaky `spacetimedb-sats` `Test Suite` failures that
occasionally end in a SIGKILL.
# Description of Changes

Fix TypeScript inference for generated object shapes containing
`Option<T>` fields.

Previously, generated TypeScript represented an optional SATS field as a
required object key whose value could be `undefined`:

```ts
{
  foo: string | undefined;
}
```

This PR changes those generated shapes to make the key itself optional:

```ts
{
  foo?: string | undefined;
}
```

The original issue was reported in #4516: generated TypeScript
reducer/procedure calls required users to pass explicit `undefined`
values for omitted optional arguments. This PR fixes that for reducer
and procedure params, and also applies the same optional-key inference
to generated row shapes.

This is a fresh bot-owned replacement for stalled PR #4518.

## Examples

A reducer with an optional argument now generates a TypeScript call
shape where the optional key may be omitted:

```rust
#[spacetimedb::reducer]
pub fn update_category(
    _ctx: &spacetimedb::ReducerContext,
    id: u64,
    name: String,
    button_text: Option<String>,
) {
    // ...
}
```

Before this PR, generated TypeScript callers had to pass the optional
argument explicitly:

```ts
await conn.reducers.updateCategory({
  id: 1n,
  name: 'updated category name',
  buttonText: undefined,
});
```

After this PR, generated TypeScript callers can omit the optional key:

```ts
await conn.reducers.updateCategory({
  id: 1n,
  name: 'updated category name',
});
```

A table row with an optional column also changes its generated
TypeScript row shape:

```rust
#[spacetimedb::table(name = player, public)]
pub struct Player {
    #[primary_key]
    pub id: u64,
    pub display_name: String,
    pub alias: Option<String>,
}
```

Before this PR, generated TypeScript treated `alias` as a required key:

```ts
type Player = {
  id: bigint;
  displayName: string;
  alias: string | undefined;
};
```

After this PR, generated TypeScript treats `alias` as an optional key:

```ts
type Player = {
  id: bigint;
  displayName: string;
  alias?: string | undefined;
};
```

# API and ABI breaking changes

This is a TypeScript source/API breaking change for generated type
shapes that contain `Option<T>` fields. It is not an ABI or wire-format
break: SATS encoding and decoding of `Option<T>` are unchanged.

The breaking edge is structural TypeScript code that requires optional
SATS fields to exist as object properties. For example, code like this
may stop compiling:

```ts
type RequiresAliasProperty = {
  id: bigint;
  displayName: string;
  alias: string | undefined;
};

function renderPlayer(player: RequiresAliasProperty) {
  return player.alias ?? player.displayName;
}

conn.db.player.onInsert((_ctx, player) => {
  renderPlayer(player);
});
```

With this PR, the generated `player` row has `alias?: string |
undefined`, so it is not assignable to a type requiring an `alias`
property.

Code using `Required<GeneratedRow>`, explicit generated-row mirror
interfaces, or generic constraints that require optional SATS fields to
be present as object keys may need to loosen those keys to optional
properties.

Reducer and procedure params are primarily loosened by this change.
Existing calls that pass `undefined` explicitly should continue to
typecheck, while calls can now omit optional keys.

# Expected complexity level and risk

2.

The change is contained to TypeScript type inference and generated
TypeScript test coverage. The main risk is source compatibility for
TypeScript consumers that depend on the old required-key shape for
`Option<T>` fields.

# Testing

- [x] `pnpm --filter @clockworklabs/test-app build`
- [x] `pnpm test`
- [x] targeted `prettier --check`
- [x] `git diff --check`

Closes #4516.

---------

Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
# Description of Changes

Moves `RelationalDB` and related database code into a new
`spacetimedb-engine` crate.
The main motivation is to tighten dependency control around the engine
layer and isolate `RelationalDB`
 behind a crate boundary.
  - Majority of this PR is code-motion.
- Removes direct production dependence on `tokio` from
`spacetimedb-engine`.
- Keeps `tokio` only as a dev-dependency for test-only code in
`spacetimedb-engine`.
- This is intended to be a structural refactor only and should not
result in any functional change in
  production.
- Adds a CI check to ensure `spacetimedb-engine` continues to compile in
simulation mode

# API and ABI breaking changes
NA

# Expected complexity level and risk
1.5.

# Testing
Existing tests should be enough.

---------

Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
# Description of Changes

<!-- Please describe your change, mention any related tickets, and so on
here. -->

This removes the `spacetimedb-update` check specifically on arm. This
test doesn't have a whole lot of value because we're already covering
Linux + Windows on x86 and then macOS on aarch64. Removing this will
allow us to decom the arm runner.

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

None - just a CI change

# Expected complexity level and risk

1

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- I have not tested this but me and Zeke sync'd on this and we think it
makes sense.
# Description of Changes

<!-- Please describe your change, mention any related tickets, and so on
here. -->

We believe this docker build is completely unused. This docker container
is different than the docker build that we send out for releases which
is the one users actually end up using. This specific docker build used
to be for internal deploys but we have not used it in a very long time
now, probably more than a year or two.

# API and ABI breaking changes

None - this is just a CI change.

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

# Expected complexity level and risk

1 - just a CI change

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- Not tested but we sync'd on this in the discord and there were no
objections from the devops team or @joshua-spacetime .

---------

Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
## What changed

Adds an explicit checkout step before `dorny/paths-filter` in the
Internal Tests workflow.

## Why

`dorny/paths-filter@v3` needs a git working tree for `push` events. The
Internal Tests workflow ran it before any checkout, so every `push` run
on `master` failed immediately in `Detect non-docs changes` with:

```text
fatal: not a git repository (or any of the parent directories): .git
```

This only showed up consistently on `master` because those runs are
`push` events. On `pull_request` events, `dorny/paths-filter` can use
the GitHub pull request files API with the PR number, so it does not
need a local checkout for the same file detection path.

Adding checkout gives the action a repository when it handles `push`
events, while leaving PR behavior unchanged.

## Testing

- `git diff --check`
- PR #5295 `Internal Tests` job completed `Checkout` and `Detect
non-docs changes` successfully, then moved on to private dispatch/wait.

---------

Signed-off-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
…#5321)

# API and ABI breaking changes

None. Docs-only change.

# Expected complexity level and risk

1 - trivial documentation fix, no code or behaviour change.

# Testing

- [x] Confirmed accessor names against the SDK source:
`database_identity()` (Rust/C++), `databaseIdentity` (TS),
`DatabaseIdentity` (C#); the old forms are marked deprecated/obsolete in
bindings.

# Description of Changes

<!-- Please describe your change, mention any related tickets, and so on
here. -->

The reducer-context cheat-sheet and the "Context Properties Reference"
tables still listed `ctx.identity` / `ctx.identity()` as the accessor
for the module's own identity. That accessor was deprecated in favour of
`databaseIdentity` / `database_identity()` / `DatabaseIdentity`, and the
troubleshooting guide added in c70d002 (#5142) documented the
deprecation but missed updating these other references — these stray
updates should have landed there. This brings all four language tabs in
line with the non-deprecated accessor.

Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
# Description of Changes

Use an isolated server process per SDK test instead of a single process
for all of the tests. In addition to reducing the memory footprint of
each test run, this should also allow for more parallelism among the
individual tests.

# API and ABI breaking changes

None

# Expected complexity level and risk

1.5

# Testing

The SDK test suite should continue to work
…on (#4888)

Re-lands #4502 on current `master` after the revert in #4881.

## Summary
- restore `spacetime lock` / `spacetime unlock`
- block deleting locked databases
- restore the database-lock smoketests

## Validation
- `cargo fmt --all --check`
- `cargo check -p spacetimedb-cli -p spacetimedb-client-api -p
spacetimedb-standalone`

---------

Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: Tyler Cloutier <cloutiertyler@aol.com>
Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com>
Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
# Description of Changes

Attempted to fix this test in #5343, but we're still getting SIGKILLS,
so ignoring for now. This will require more investigation to fix. I've
included what I believe is the reason in the help text and created
[this](#5362) tracker to unskip.

# API and ABI breaking changes

None

# Expected complexity level and risk

0

# Testing

N/A
# Description of Changes

Reviewing recent benchmark runs, it appears that #5071 probably
regressed TPS by around 3-5%. I don't want to revert that change because
it has implications for replication, and so for now we'll just live with
the slight regression.

# API and ABI breaking changes

None

# Expected complexity level and risk

0

# Testing

N/A
# Description of Changes

Closes #5250

#4636 introduced a regression where the ProcedureContext used to include
the sender and connection_id from the caller while now it is always
empty (which is wrong)

Correct it.

Also fully migrate to `database_identity` which was forgotten about so i
deprecated it for procedures (since they are now stable) and just
changed it for HttpHandlers (because they are still unstable)

@gefjon since you did the lil woopsie (ugh pinging you again lol hope im
not annoying haha)

# API and ABI breaking changes

Breaking the HttpHandler function which is unstable.

Restoring behaviour of 2.3 which got lost with 2.4.
<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

# Expected complexity level and risk

1. Trivial refactoring 
<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [x] The caller identity is there again for Procedures.

---------

Signed-off-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
…o website (#5264)

# Description of Changes
- spacetime init --template without arg prints templates list and link
to website

# Screenshot
<img width="696" height="392" alt="screenshot"
src="https://github.com/user-attachments/assets/98e87537-554b-411b-96ab-3ceb9a6a9d45"
/>

<!-- Please describe your change, mention any related tickets, and so on
here. -->

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

# Expected complexity level and risk
1

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [x] I tested the changes
)

# Description of Changes

This is an attempt at fixing a SIGABRT that sometimes happens when
running the standalone integration tests.

It's not clear exactly what causes it, however one thing that may have
contributed to it was that previously the tests did not initiate a clean
shutdown of the database.

Some modules schedule repeating work in `init`, and it's not obvious to
me that in flight operations will exit cleanly if we just drop all of
the database/runtime handles at once.

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1

# Testing

This is a testing fix.
…5283)

# Description of Changes

Refactors the Rust smoketest helper surface so publish and subscribe
variants use builders instead of parallel helper methods.

- Replaces `publish_module*` helper variants with `Smoketest::publish()`
and fluent options for name, clear, current database, break clients,
stdin, replicas, organization, and source modules.
- Replaces `subscribe_*` / `subscribe_background*` variants with
`Smoketest::subscribe(...).expect_rows(...).confirmed(...).background()`.
- Updates smoketest call sites to the builder APIs.

# API and ABI breaking changes

Internal smoketest helper API change only. No product API or ABI
changes.

# Expected complexity level and risk

2

This touches many smoketest call sites, but the underlying CLI command
behavior remains centralized in the same helper internals.

# Testing
- [x] Existing CI passes

---------

Signed-off-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
# Description of Changes

Adds a merge-queue fast path for CI when the synthetic merge-group
commit has the same tree as the queued PR head.

The new `merge_queue_noop` job parses the PR number from the merge-group
ref, resolves the PR head SHA, and compares that tree to `GITHUB_SHA`.
If there is no diff, the expensive CI jobs are skipped as duplicate
work. Matrix jobs with required per-matrix check names get lightweight
no-op counterparts so branch protection still sees the expected
successful check names.

# API and ABI breaking changes

None.

# Expected complexity level and risk

2. This is limited to GitHub Actions wiring, but it interacts with merge
queue semantics and required check names. The implementation
intentionally falls back to normal CI if it cannot parse the PR number
or resolve the PR head.

# Testing

- [x] Parsed `.github/workflows/ci.yml` with Ruby YAML.
- [x] Ran `git diff --check`.

---------

Signed-off-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
@aasoni aasoni force-pushed the alessandro/handle-module-mounts-in-migrations branch from 661c7f4 to 48361bf Compare June 18, 2026 07:29
@aasoni aasoni requested a review from joshua-spacetime as a code owner June 18, 2026 08:47
@aasoni aasoni force-pushed the alessandro/handle-module-mounts-in-migrations branch from 7eabdf4 to a63c87b Compare June 18, 2026 11:42
@JasonAtClockwork

Copy link
Copy Markdown
Contributor

I've run through and ran into two problems which I'll list out as separate comments here, I've used AI to minify the repro to hopefully make this easier to check on your end:

  1. Call a child submodule reducer from inside its parent submodule reducer using ctx.as.child, and that call fails at runtime.
  // src/index.ts
  import { schema } from 'spacetimedb/server';
  import * as nested from './nested';

  const root = schema({ nested });
  export default root;

  // src/nested/index.ts
  import { schema, t } from 'spacetimedb/server';
  import * as child from './child';

  const nested = schema({ child });
  export default nested;

  export const parent_calls_child = nested.reducer(
    { value: t.string() },
    (ctx, { value }) => {
      child.child_insert(ctx.as.child, { value });
    }
  );

  // src/nested/child.ts
  import { schema, table, t } from 'spacetimedb/server';

  const child_events = table(
    { name: 'child_events', public: true },
    { id: t.u64().primaryKey().autoInc(), value: t.string() }
  );

  const child = schema({ child_events });
  export default child;

  export const child_insert = child.reducer(
    { value: t.string() },
    (ctx, { value }) => {
      ctx.db.child_events.insert({ id: 0n, value });
    }
  );

Then:

  spacetimedb-cli call <db> nested.child.child_insert direct-child
  spacetimedb-cli call <db> nested.parent_calls_child hello

  Expected broken-runtime result:

  nested.child.child_insert succeeds

  nested.parent_calls_child fails:
  Cannot read properties of undefined (reading 'db')
  at child_insert
  at parent_calls_child

@JasonAtClockwork

Copy link
Copy Markdown
Contributor
  1. Procedure in parent submodule fails if the submodule has child namespaces
  // src/index.ts
  import { schema } from 'spacetimedb/server';
  import * as nested from './nested';

  const root = schema({ nested });
  export default root;

  // src/nested/index.ts
  import { schema, table, t } from 'spacetimedb/server';
  import * as child from './child';

  const parent_events = table(
    { name: 'parent_events', public: true },
    { id: t.u64().primaryKey().autoInc(), value: t.string() }
  );

  const nested = schema({ parent_events, child });
  export default nested;

  export const parent_count = nested.procedure(t.u64(), ctx =>
    ctx.withTx(tx => tx.db.parent_events.count())
  );

  // src/nested/child.ts
  import { schema, table, t } from 'spacetimedb/server';

  const child_events = table(
    { name: 'child_events', public: true },
    { id: t.u64().primaryKey().autoInc(), value: t.string() }
  );

  const child = schema({ child_events });
  export default child;

Then:

  spacetimedb-cli call <db> nested.parent_count

  fails with:

  Uncaught No such table
  at makeTableView
  at buildDbViewForDispatch
  at buildAliasCtx
  at buildAliasCtxMap
  at assignTxAliasViews
  at withTx
  at src/nested/index.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.